4

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.

lorg
  • 1,160
  • 1
  • 10
  • 27
  • Python variables cannot be uninitialized because they are created by initialization. If a variable exists, it has a none-None value, unless you explicitly set it to None. – DYZ Aug 03 '21 at 17:58
  • 1
    Do you mean like https://docs.python.org/3/library/asyncio-future.html? It's not a general attribute you would apply to any type, but a type in and of itself (values of which can produce values of other types). – chepner Aug 03 '21 at 17:59
  • Best that I have found is something like `typing.cast(myvar, str)` at the place where `myvar` is declared. – 0x5453 Aug 03 '21 at 17:59
  • 3
    Or just that `myvar` will be a string once I assign to it? Given that Python doesn't require variables to be declared, `myvar: str` is sufficient. You don't have to initialize the variable first. – chepner Aug 03 '21 at 18:00
  • python typing support only supports ahead of time static type checking. there is not much sense in telling it that it will eventually be correct at runtime. – Aaron Aug 03 '21 at 18:00
  • Twisted offers [deferreds](https://twistedmatrix.com/documents/16.4.1/core/howto/defer-intro.html) – ti7 Aug 03 '21 at 18:01
  • I added a code example that clarifies the problem – lorg Aug 03 '21 at 18:26

3 Answers3

3

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.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • I think it would be good to say that in Python there _usually_ isn't a reason to annotate a variable, but especially not before it's defined. That being said, it can be helpful in less common situations that mypy struggles with. – Kyle Parsons Aug 03 '21 at 18:12
  • This is problematic because if I were to do @dataclass class Flag: name: str = dataclasses.field(init=False, default=None) The type checker would complain that the default value is not compatible with the type. – lorg Aug 03 '21 at 18:32
  • 2
    Well, yes. If you are explicitly saying the default can by `None`, then `str` is the wrong type. Either use `Optional[str]`, or provide a default string value (like `""`). If you want to say that the field *has* no default value (i.e., it's required), the correct sentinel is `dataclasses.MISSING`, not `None`. (And `MISSING` is the default default when no explicit default is provided.) – chepner Aug 03 '21 at 18:59
2

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
lorg
  • 1,160
  • 1
  • 10
  • 27
  • 1
    makes sense - you probably also want to provide [@name.setter](https://stackoverflow.com/questions/1684828/how-to-set-attributes-using-property-decorators) too! – ti7 Aug 03 '21 at 18:31
  • 1
    An easy way to reduce the runtime cost of this is by adding the `@functools.cache` decorator in between the `@property` decorator and the function definition. – Alex Waygood Aug 05 '21 at 21:42
-1

You can await awaitable objects, allowing another coroutine to run

async def my_funcion(*args):
    ...

value = await my_function(*args)
ti7
  • 16,375
  • 6
  • 40
  • 68