Indeed, this only creates an annotation for the attribute, it does not create the attribute itself. Attributes and variables are only created by assignment, and nothing's being assigned here, so it doesn't exist (not even with an implicit None
or such).
This pattern is useful to satisfy type checkers if the attribute is initialised outside of __init__
, e.g.:
class MyClass(SomeParentClass):
aa: int
def initialize(self):
self.aa = 'foo'
Let's say that SomeParentClass
will call initialize
at some defined point during its instantiation process and it wants subclasses to use initialize
to do their initialisations, instead of overriding __init__
. A type checker might complain here that aa
is created outside of __init__
and is therefore not safe to access. The aa: int
annotation explicitly says that aa
should be expected to exist as an int
at any time, so is safe to access (taking care that that'll actually be the case is your responsibility then). An example of this kind of pattern can be found in Tornado, for instance.
Another use of these annotations of course are classes where those annotations are explicitly used at runtime, like Python's own dataclasses
do.