diff --git a/pep-0526.txt b/pep-0526.txt index ba31b53b560..7ca20a33b0e 100644 --- a/pep-0526.txt +++ b/pep-0526.txt @@ -85,7 +85,7 @@ a core part of the language. Non-goals ********* -While the proposal is accompanied by an addition of ``inspect.getannotations`` +While the proposal is accompanied by an extension of ``typing.get_type_hints`` standard library function for runtime retrieval of annotations, the variable annotations are not designed for runtime type checking. Third party packages would have to be developed to implement such functionality. @@ -99,8 +99,17 @@ easy way to specify the structured type metadata for third party tools. Specification ============= -*** big key concepts, not quite sure what the best way to organize this would -be, or if they deserve their own sections *** +Type annotation could be added to an assignment statement or to a simple +name indicating the desired type of the annotation target to a third +party type checker:: + + my_var: int + my_var = 5 # Passes type check. + other_var: int = 'a' # Flagged as error by type checker, + # but OK at runtime. + +Below we specify the semantics of type annotations for type checkers +in different contexts and their runtime effects. Variable Annotations ******************** @@ -119,7 +128,7 @@ assigned in conditional branches:: else: sane_world = False -Note that, although this syntax does allow tuple packing, it does *not* allow +Note that, although the syntax does allow tuple packing, it does *not* allow one to annotate the types of variables when tuple unpacking is used:: # Tuple packing with variable annotation syntax @@ -231,14 +240,20 @@ any valid assignment target:: class Cls: pass - c = Cls - c.x: int = 0 # Annotates ``c.x`` with ``int``.. + c = Cls() + c.x: int = 0 # Annotates c.x with int. c.y: int # Invalid syntax: no initial value was specified! d = {} - d['a']: int = 0 # Annotates ``d['a']`` with ``int``. + d['a']: int = 0 # Annotates d['a'] with int. d['b']: int # Invalid again. +Note that even ``(my_var)`` is considered an expression, not a simple name. +Consequently:: + + (x): int # Invalid syntax + (x): int = 0 # OK + Where annotations aren't allowed ******************************** @@ -247,10 +262,15 @@ It's illegal to attempt to annotate ``global`` and ``nonlocal``:: def f(): global x: int # SyntaxError + def g(): + x: int # Also a SyntaxError + global x + The reason is that ``global`` and ``nonlocal`` don't own variables; therefore, the type annotations belong in the scope owning the variable. -In addition, you cannot annotate variable used in a ``for`` or ``with`` +Only single assignment targets and single right hand side values are allowed. +In addition, one cannot annotate variables used in a ``for`` or ``with`` statement; they can be annotated ahead of time, in a similar manner to tuple unpacking:: @@ -270,12 +290,13 @@ Changes to standard library and documentation instances. This restriction is ensured by static checkers, but not at runtime. -- Function ``getannotations`` is added to the ``inspect`` module that is used - to retrieve annotations at runtime from modules, classes, and functions. +- Function ``get_type_hints`` in the ``typing`` module will be extended, + so that one can retrieve type annotations at runtime from modules + and classes in addition to functions. Annotations are returned as a dictionary mapping from variable, arguments, - or attributes to their type hints. For classes it returns - ``collections.ChainMap`` constructed from annotations in method - resolution order of that class. + or attributes to their type hints with forward refs evaluated. + For classes it returns ``collections.ChainMap`` constructed from + annotations in method resolution order of that class. - Recommended guidelines for using annotations will be added to the documentation, containing a pedagogical recapitulation of specifications @@ -287,11 +308,9 @@ Changes to standard library and documentation Runtime effects of type annotations =================================== -As stated under "Variable Annotations", annotating a local variable will cause +Annotating a local variable will cause the interpreter to treat it as a local, even if it was never assigned to. - -If a variable annotation is for a local variable, the annotation will not be -evaluated:: +Annotations for local variables will not be evaluated:: def f(): x: NonexistentName # No error. @@ -317,12 +336,11 @@ from names to evaluated annotations. Here is an example:: # prints: {'players': typing.Dict[str, __main__.Player]} The recommended way of getting annotations at runtime is by using -``inspect.getannotations`` function; as with all dunder attributes, +``typing.get_type_hints`` function; as with all dunder attributes, any undocummented use of ``__annotations__`` is subject to breakage without warning:: - from typing import Dict, ClassVar - from inspect import getannotations + from typing import Dict, ClassVar, get_type_hints class Starship: hitpoints: int = 50 stats: ClassVar[Dict[str, int]] = {} @@ -331,12 +349,12 @@ without warning:: def __init__(self, captain: str) -> None: ... - assert getannotations(Starship) == {'hitpoints': int, + assert get_type_hints(Starship) == {'hitpoints': int, 'stats': ClassVar[Dict[str, int]], 'shield': int, 'captain': str} - assert getannotations(Starship.__init__) == {'captain': str, + assert get_type_hints(Starship.__init__) == {'captain': str, 'return': None} Note that if annotations are not found statically, then the @@ -368,7 +386,15 @@ preferred use of annotations. Rejected proposals and things left out for now -============================================= +============================================== + +- **Do not introduce variable annotations at all:** + Variable annotations have *already* been around for almost two years + in the form of type comments. They are extensively used by third + party type checkers (mypy, pytype, etc) and by projects using the + type checkers. However, the comment syntax has many downsides + listed in Rationale. This PEP is not about the need for type + annotations, it is about what should be the syntax for such annotations. - **Introduce a new keyword:** First, choice of a good keyword is hard, @@ -436,6 +462,12 @@ Rejected proposals and things left out for now declarations together in the class makes it easier to find them, and helps a first-time reader of the code. +- **Use syntax** ``x: class t = v`` **for class variables:** + This will require a more complicated parser and will confuse simple + minded syntax highlighters. Anyway we need to have ``ClassVar`` to + store class variables to ``__annotations__``, so that it was decided + to go with a simpler syntax. + - **Forget about** ``ClassVar`` **altogether:** This was proposed since mypy seems to be getting along fine without a way to distinguish between class and instance variables. But a type checker