What Are Variable Annotations

What are variable annotations?

Everything between : and the = is a type hint, so primes is indeed defined as List[int], and initially set to an empty list (and stats is an empty dictionary initially, defined as Dict[str, int]).

List[int] and Dict[str, int] are not part of the next syntax however, these were already defined in the Python 3.5 typing hints PEP. The 3.6 PEP 526 – Syntax for Variable Annotations proposal only defines the syntax to attach the same hints to variables; before you could only attach type hints to variables with comments (e.g. primes = [] # List[int]).

Both List and Dict are Generic types, indicating that you have a list or dictionary mapping with specific (concrete) contents.

For List, there is only one 'argument' (the elements in the [...] syntax), the type of every element in the list. For Dict, the first argument is the key type, and the second the value type. So all values in the primes list are integers, and all key-value pairs in the stats dictionary are (str, int) pairs, mapping strings to integers.

See the typing.List and typing.Dict definitions, the section on Generics, as well as PEP 483 – The Theory of Type Hints.

Like type hints on functions, their use is optional and are also considered annotations (provided there is an object to attach these to, so globals in modules and attributes on classes, but not locals in functions) which you could introspect via the __annotations__ attribute. You can attach arbitrary info to these annotations, you are not strictly limited to type hint information.

You may want to read the full proposal; it contains some additional functionality above and beyond the new syntax; it specifies when such annotations are evaluated, how to introspect them and how to declare something as a class attribute vs. instance attribute, for example.

Variable annotations on a class

This can be done using forward references. So your code would look like this:

class MyObject:
parent: 'MyObject' = None

Get variable annotations from python class variables

You can use typing.get_args(annotation) to get a tuple of the arguments to a type. For instance:

class User:
username: Annotated[str, 'exposed'] = "admin"
password: Annotated[str, 'hidden'] = "admin"

>>> typing.get_args(User.__annotations__["username"])
(<class 'str'>, 'exposed')
>>> typing.get_args(User.__annotations__["username"])[1]
'exposed'

Annotate dataclass class variable with type value

At the end I just replaced the variable in _data_cls annotation with the base class and fixed the annotation of subclasses as noted by @rv.kvetch in his answer.

The downside is the need to define the result class twice in every subclass, but in my opinion it is more legible than extracting the class in property.

The complete solution:

from dataclasses import dataclass
from typing import ClassVar, Generic, Optional, Sequence, Type, TypeVar


class ResultData:
...


T = TypeVar('T', bound=ResultData)


@dataclass
class Result(Generic[T]):
_data_cls: ClassVar[Type[ResultData]] # Fixed annotation here
data: Sequence[T]

@classmethod
def parse(cls, ...) -> T:
self = cls()
self.data = [self._data_cls.parse(...)]
return self

class FooResultData(ResultData):
...

class FooResult(Result[FooResultData]): # Fixed annotation here
_data_cls = FooResultData

Python does not allow annotating the types of variables when unpacking

There is some information about this in the Rejected/Postponed Proposals section of PEP 526:

Allow type annotations for tuple unpacking: This causes ambiguity: it's not clear what this statement means:

x, y: T

Are x and y both of type T, or do we expect T to be a tuple type of two items that are distributed over x and y, or perhaps x has type Any and y has type T? (The latter is what this would mean if this occurred in a function signature.) Rather than leave the (human) reader guessing, we forbid this, at least for now.

The comment seems to suggest there may eventually be a proposal for a simpler syntax that isn't as prone to being misconstrued. For now, we're left with having to annotate the tuple's types separately.

How to inject class-variable annotations in Python 3.7+?

The information used in variable annotations for instance and class attributes is stored in the __annotations__ mapping on the class, a dictionary that's writable.

If you want to add to the information stored there, then just add your new information directly to that mapping:

A.__annotations__['bar'] = float

The A.bar: float annotation is discarded by Python, as there is no dedicated location to store the information for annotated expressions; it is up to the static type checker implementation to decide if that expression has meaning.

See the Runtime Effects of Type Annotations section of PEP 526 -- Syntax for Variable Annotations, the document that defines this syntax:

In addition, at the module or class level, if the item being annotated is a simple name, then it and the annotation will be stored in the __annotations__ attribute of that module or class (mangled if private) as an ordered mapping from names to evaluated annotations.

and from the Annotated assignment statements section of the Python reference documentation:

For simple names as assignment targets, if in class or module scope, the annotations are evaluated and stored in a special class or module attribute __annotations__ that is a dictionary mapping from variable names (mangled if private) to evaluated annotations. This attribute is writable and is automatically created at the start of class or module body execution, if annotations are found statically.

A.bar is not a simple name, it is an expression, so it is not stored; if you want to retain that information in the __annotations__ mapping for runtime access, then manually setting it is the only option.



Related Topics



Leave a reply



Submit