Python typing support for not-None "late" variables

Viewed 337

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.

3 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.

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

You can await awaitable objects, allowing another coroutine to run

async def my_funcion(*args):
    ...

value = await my_function(*args)
Related