Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python typing support for not-None "late" variables

Dart has "late variables" , swift has "Implicitly Unwrapped Optionals".

Does Python have something equivalent?

That means, something like myvar: Late[str] = None that would indicate that I know that all access to myvar later on will be after it is initialized to a non-None value.

My use case:

@dataclass
class Flag:
    name: Optional[str] = dataclasses.field(init=False, default=None)
    default_value: Any
    value_type: Type
    description: str = ""

Flag.name is initialized by a "friend" class, and all access to Flag is through that friend class, so sure that outside of this module all access is not to an Optional, but to an actual str.

like image 958
lorg Avatar asked Sep 02 '25 16:09

lorg


2 Answers

Python doesn't have declarations, only definitions. You can write something like

# *Looks* like a declaration, but does not create a variable
# named myvar, only annotates the name.
myvar: str

which tells any static type checker that myvar, when finally used, will have a str value, but doesn't actually create the variable yet. The annotation itself has no meaning at runtime, aside from possibly updating the global __annotations__ dict.

Later on, an initial assignment like myvar = "foo" will be accepted by a static type checker, but an initial assignment like myvar = 1 will be rejected.


Such non-assigned annotated names are rarely necessary in Python. Their biggest use is in the body of a class statement decorated by dataclass, which allows you to define the instance attributes for instances of the class. (This information gets used when generating methods like __init__.)

@dataclass
class Foo:
     x: int
     y: str = "foo"
     z = 3

The type itself isn't terribly important; it's the presence of a type that causes dataclass to generate an __init__ method like

def __init__(self, x: int, y: str = "foo"):
    self.x = x
    self.y = y

The unannotated name z is taken as an ordinary class attribute.

like image 88
chepner Avatar answered Sep 05 '25 05:09

chepner


I found the following not-the-best solution. I made the relevant member "protected" - changed it to _name, and wrapped it with a property.

The runtime check is only there to quiet down the type checker without shutting it down, and it does afford a little bit of extra protection if someone does misuse this, at a relatively small runtime speed code (that I don't care to optimize right now).

@dataclass
class Flag:
    _name: Optional[str] = dataclasses.field(init=False, default=None)
    default_value: Any
    value_type: Type
    description: str = ""

    @property
    def name(self) -> str:
        if self._name is None:
            raise RuntimeError("self._name not initialized")
        return self._name
like image 22
lorg Avatar answered Sep 05 '25 06:09

lorg