Type annotations for variables without initial value

1.1k Views Asked by At

I've been reading through PEP484 and 526 and still can't figure out what is a better way to do type annotations for variables without an initial value.

Say, you have a class and in the __init__ you want to declare a variable, but without providing an initial value where assignment happens later in the code. Usually what I would normally do is:

from typing import Optional, List

class SomeClass:

    def __init__(self) -> None:
        self.some_value: Optional[int] = None
        self.other_var: Optional[List] = None

    def _some_method(self) -> None:
        self.some_value = 42

This works, but I feel like using Optional makes __init__ overly busy and harder to read. Instead what I could do is:

from typing import List

class SomeClass:

    def __init__(self) -> None:
        self.some_value: int
        self.other_var: List

    def _some_method(self) -> None:
        self.some_value = 42

Obviously, this leaves variables uninitialized, but as long as they are correctly assigned to before being referenced, everything works just fine and makes it easier to read if I have a lot of class attributes declared in __init__.

Are there any specific issues that could arise from leaving variables uninitialized when using the second approach?

2

There are 2 best solutions below

0
On

Are there any specific issues that could arise from leaving variables uninitialized when using the second approach?

Yes. Adding type annotations without setting a value is for the type checker only. You won't actually create the variable. Just have a look at the error thrown from your second example:

from typing import List


class SomeClass:
    def __init__(self) -> None:
        self.some_value: int
        self.other_var: List

    def _some_method(self) -> None:
        self.some_value = 42


sc = SomeClass()
print(sc.some_value)
Traceback (most recent call last):
  File ".../test.py", line 14, in <module>
    print(sc.some_value)
          ^^^^^^^^^^^^^
AttributeError: 'SomeClass' object has no attribute 'some_value'

Note that the error is "has no attribute". In other words, the attribute was not created at all.

As of Python 3.10 the syntax has gotten a little cleaner, so you don't need the import from typing anymore, and you don't need Optional anymore. This is how I'd type your class:

class SomeClass:
    def __init__(self) -> None:
        self.some_value: int | None = None
        self.other_var: list[int] | None = None

    def _some_method(self) -> None:
        self.some_value = 42
0
On

If you don't like how crowded the __init__ method gets, you could split between private and public attributes and annotate only the latter.

class SomeClass:
    def __init__(self) -> None:
        self._some_value = None
    
    @property
    def some_value(self) -> Optional[int]:  # int | None in Python >= 3.10
        return self._some_value
    
    @some_value.setter
    def some_value(self, value):
        self._some_value = value

    def _some_method(self) -> None:
        self.some_value = 42