diff --git a/pep-0526.txt b/pep-0526.txt index c14cd4050..7c1e11621 100644 --- a/pep-0526.txt +++ b/pep-0526.txt @@ -1,5 +1,5 @@ PEP: 526 -Title: Syntax for Variable and Attribute Annotations +Title: Syntax for Variable Annotations Version: $Revision$ Last-Modified: $Date$ Author: Ryan Gonzalez , Philip House , Ivan Levkivskyi , Lisa Roach , Guido van Rossum @@ -38,11 +38,12 @@ type comments to annotate variables:: captain = ... # type: str class Starship: - # 'stats' is a class attribute + # 'stats' is a class variable stats = {} # type: Dict[str, int] This PEP aims at adding syntax to Python for annotating the types of variables -and attributes, instead of expressing them through comments:: +(including non-method attributes), instead of expressing them through +comments:: primes: List[int] = [] @@ -60,7 +61,7 @@ expressed through comments has some downsides: - Text editors often highlight comments differently from type annotations. -- There's no way to annotate the type of an undefined variable; you need to +- There's no way to annotate the type of an undefined variable; one needs to initialize it to ``None`` (e.g. ``a = None # type: int``). - Variables annotated in a conditional branch are difficult to read:: @@ -88,7 +89,10 @@ expressed through comments has some downsides: which is inelegant, to say the least. The majority of these issues can be alleviated by making the syntax -a core part of the language. +a core part of the language. Moreover, having a dedicated annotation syntax +for class and instance variables (in addition to method annotations) will +pave the way to static duck-typing as a complement to nominal typing defined +by PEP 484. Non-goals ********* @@ -103,12 +107,16 @@ language, and the authors have no desire to ever make type hints mandatory, even by convention.** The goal of annotation syntax is to provide an easy way to specify the structured type metadata for third party tools. +This PEP does not require type checkers to change their type checking +rules. It merely provides a more readable syntax to replace type +comments. + Specification ============= -Type annotation can be added to an assignment statement or to a simple -name indicating the desired type of the annotation target to a third +Type annotation can be added to an assignment statement or to a single +expression indicating the desired type of the annotation target to a third party type checker:: my_var: int @@ -116,11 +124,15 @@ party type checker:: 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 +Below we specify the syntax of type annotations in different contexts and their runtime effects. -Variable Annotations -******************** +We also suggest how type checkers might interpret annotations, but +compliance to these suggestions is not mandatory. (This is in line +with the attitude towards compliance in PEP 484.) + +Global and local variable annotations +************************************* The types of locals and globals can be annotated as follows:: @@ -159,7 +171,7 @@ it a local:: def f(): a: int print(a) # raises UnboundLocalError - # Commenting out the ``a: int`` makes it a NameError. + # Commenting out the a: int makes it a NameError. as if the code were:: @@ -168,44 +180,29 @@ as if the code were:: print(a) # raises UnboundLocalError Duplicate type annotations will be ignored. However, static type -checkers will issue a warning for annotations of the same variable +checkers may issue a warning for annotations of the same variable by a different type:: a: int - a: str # Static type checker will warn about this. + a: str # Static type checker may or may not warn about this. -``__annotations__`` is writable, so this is permitted:: +.. _classvar: - __annotations__['s'] = str +Class and instance variable annotations +*************************************** -But attempting to update ``__annotations__`` to something other than a dict -may result in a TypeError:: - - class C: - __annotations__ = 42 - x: int = 5 # raises TypeError - -(Note that the assignment to ``__annotations__``, which is the -culprit, is accepted by the Python interpreter without questioning it --- but the subsequent type annotation expects it to be a -``MutableMapping`` and will fail.) - - -Attribute annotations -********************* - -Type annotations can also be used to annotate attributes -in class bodies. In particular, the value-less notation ``a: int`` allows us -to annotate instance variables that should be initialized in ``__init__`` -or ``__new__``. The proposed syntax is as follows:: +Type annotations can also be used to annotate class and instance variables +in class bodies and methods. In particular, the value-less notation ``a: int`` +allows one to annotate instance variables that should be initialized +in ``__init__`` or ``__new__``. The proposed syntax is as follows:: class BasicStarship: captain: str = 'Picard' # instance variable with default damage: int # instance variable without default - stats: ClassVar[Dict[str, int]] = {} # class variable + stats: ClassVar[Dict[str, int]] = {} # class variable Here ``ClassVar`` is a special class in typing module that indicates to -static type checker that this attribute should not be set on class instances. +static type checker that this variable should not be set on instances. This could be illustrated with a more detailed example. In this class:: class Starship: @@ -228,7 +225,7 @@ is truly a class variable -- it is intended to be shared by all instances. Since both variables happen to be initialized at the class level, it is useful to distinguish them by marking class variables as annotated with types wrapped in ``ClassVar[...]``. In such way type checker will prevent -accidental assignments to attributes with a same name on class instances. +accidental assignments to attributes with a same name on instances. For example, annotating the discussed class:: class Starship: @@ -246,7 +243,7 @@ For example, annotating the discussed class:: enterprise_d.stats = {} # Flagged as error by a type checker Starship.stats = {} # This is OK -As a matter of convenience, instance attributes can be annotated in +As a matter of convenience, instance variables can be annotated in ``__init__`` or other methods, rather than in class:: from typing import Generic, TypeVar @@ -259,27 +256,24 @@ As a matter of convenience, instance attributes can be annotated in Annotating expressions ********************** -If the initial value is specified, then the target of the annotation can be -any valid single assignment target:: +The target of the annotation can be any valid single assignment target:: class Cls: pass c = Cls() c.x: int = 0 # Annotates c.x with int. - c.y: int # Invalid syntax: no initial value was specified! + c.y: int # Annotates c.y with int. d = {} d['a']: int = 0 # Annotates d['a'] with int. - d['b']: int # Invalid again. + d['b']: int # Annotates d['b'] with int. -Note that even ``(my_var)`` is considered an expression, not a simple name. -Consequently:: +Note that even a parenthesized name considered an expression, +not a simple name:: - (x): int # Invalid syntax - (x): int = 0 # OK - -It is up to the type checker to decide exactly when to accept this syntax. + (x): int # Annotates x with int, (x) treated as expression by compiler. + (y): int = 0 # Same situation here. Where annotations aren't allowed ******************************** @@ -311,22 +305,23 @@ unpacking:: ... -Changes to standard library and documentation +Changes to Standard Library and Documentation ============================================= - A new covariant type ``ClassVar[T_co]`` is added to the ``typing`` module. It accepts only a single argument that should be a valid type, and is used to annotate class variables that should no be set on class instances. This restriction is ensured by static checkers, - but not at runtime. See Attribute Annotations for examples - and explanations for the usage of ``ClassVar``, and see the Rejected - Proposals section for more information on the reasoning behind ``ClassVar``. + but not at runtime. See the + `classvar`_ section for examples and explanations for the usage of + ``ClassVar``, and see the `rejected`_ section for more information on + the reasoning behind ``ClassVar``. - 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 with forward references evaluated. + Annotations are returned as a dictionary mapping from variable or arguments + to their type hints with forward references evaluated. For classes it returns a mapping (perhaps ``collections.ChainMap``) constructed from annotations in method resolution order. @@ -337,7 +332,7 @@ Changes to standard library and documentation separately from the standard library. -Runtime effects of type annotations +Runtime Effects of Type Annotations =================================== Annotating a local variable will cause @@ -352,10 +347,10 @@ evaluated:: x: NonexistentName # Error! class X: - attr: NonexistentName # Error! + var: NonexistentName # Error! 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 +*simple name*, then it and the annotation will be stored in the ``__annotations__`` attribute of that module or class as a dictionary mapping from names to evaluated annotations. Here is an example:: @@ -367,6 +362,22 @@ from names to evaluated annotations. Here is an example:: print(__annotations__) # prints: {'players': typing.Dict[str, __main__.Player]} +``__annotations__`` is writable, so this is permitted:: + + __annotations__['s'] = str + +But attempting to update ``__annotations__`` to something other than a dict +may result in a TypeError:: + + class C: + __annotations__ = 42 + x: int = 5 # raises TypeError + +(Note that the assignment to ``__annotations__``, which is the +culprit, is accepted by the Python interpreter without questioning it +-- but the subsequent type annotation expects it to be a +``MutableMapping`` and will fail.) + The recommended way of getting annotations at runtime is by using ``typing.get_type_hints`` function; as with all dunder attributes, any undocummented use of ``__annotations__`` is subject to breakage @@ -416,9 +427,10 @@ These stored annotations might be used for other purposes, but with this PEP we explicitly recommend type hinting as the preferred use of annotations. +.. _rejected: -Rejected proposals and things left out for now -============================================== +Rejected/Postponed Proposals +============================ - **Should we introduce variable annotations at all?** Variable annotations have *already* been around for almost two years @@ -495,11 +507,11 @@ Rejected proposals and things left out for now would needed to be checked for everywhere in the code. Therefore, Guido just said plain "No" to this. -- **Add also** ``InstanceAttr`` **to the typing module:** +- **Add also** ``InstanceVar`` **to the typing module:** This is redundant because instance variables are way more common than class variables. The more common usage deserves to be the default. -- **Allow instance attribute annotations only in methods:** +- **Allow instance variable annotations only in methods:** The problem is that many ``__init__`` methods do a lot of things besides initializing instance variables, and it would be harder (for a human) to find all the instance variable declarations. @@ -529,7 +541,7 @@ Rejected proposals and things left out for now are always evaluated. Although this might be reconsidered in future, it was decided in PEP 484 that this would have to be a separate PEP. -- **Declare attribute types in class docstring:** +- **Declare variable types in class docstring:** Many projects already use various docstring conventions, often without much consistency and generally without conforming to the PEP 484 annotation syntax yet. Also this would require a special sophisticated parser.