From e48206a7da01248b6ba380506606912301d0964b Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 2 Sep 2016 15:03:41 -0700 Subject: [PATCH] PEP 526 updates (#84) --- pep-0526.txt | 83 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/pep-0526.txt b/pep-0526.txt index 7c1e11621..75898b1a4 100644 --- a/pep-0526.txt +++ b/pep-0526.txt @@ -8,7 +8,7 @@ Type: Standards Track Content-Type: text/x-rst Created: 09-Aug-2016 Python-Version: 3.6 -Post-History: 30-Aug-2016 +Post-History: 30-Aug-2016, 02-Sep-2016 Notice for Reviewers @@ -21,7 +21,7 @@ There was preliminary discussion on python-ideas and at https://github.com/python/typing/issues/258. Before you bring up an objection in a public forum please at least -read the summary of rejected ideas listed at the end of this PEP. +read the summary of `rejected`_ ideas listed at the end of this PEP. Abstract @@ -42,8 +42,8 @@ type comments to annotate variables:: stats = {} # type: Dict[str, int] This PEP aims at adding syntax to Python for annotating the types of variables -(including non-method attributes), instead of expressing them through -comments:: +(including class variables and instance variables), +instead of expressing them through comments:: primes: List[int] = [] @@ -52,6 +52,12 @@ comments:: class Starship: stats: ClassVar[Dict[str, int]] = {} +PEP 484 explicitly states that type comments are intended to help with +type inference in complex cases, and this PEP does not change this +intention. However, since in practice type comments have also been +adopted for class variables and instance variables, this PEP also +discusses the use of type annotations for those variables. + Rationale ========= @@ -72,14 +78,14 @@ expressed through comments has some downsides: my_var = another_function() # Why isn't there a type here? - Since type comments aren't actually part of the language, if a Python script - wants to parse them, it would require a custom parser instead of just using + wants to parse them, it requires a custom parser instead of just using ``ast``. - Type comments are used a lot in typeshed. Migrating typeshed to use the variable annotation syntax instead of type comments would improve readability of stubs. -- In situations where normal comments and type comments used together, it is +- In situations where normal comments and type comments are used together, it is difficult to distinguish them:: path = None # type: Optional[str] # Path to module source @@ -97,15 +103,15 @@ by PEP 484. Non-goals ********* -While the proposal is accompanied by an extension of ``typing.get_type_hints`` -standard library function for runtime retrieval of annotations, the variable +While the proposal is accompanied by an extension of the ``typing.get_type_hints`` +standard library function for runtime retrieval of annotations, variable annotations are not designed for runtime type checking. Third party packages -would have to be developed to implement such functionality. +will have to be developed to implement such functionality. It should also be emphasized that **Python will remain a dynamically typed 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. +easy way to specify 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 @@ -136,8 +142,8 @@ Global and local variable annotations The types of locals and globals can be annotated as follows:: - some_number: int # variable without default - some_list: List[int] = [] # variable with default + some_number: int # variable without initial value + some_list: List[int] = [] # variable with initial value Being able to omit the initial value allows for easier typing of variables assigned in conditional branches:: @@ -160,7 +166,7 @@ one to annotate the types of variables when tuple unpacking is used:: body: Optional[List[str]] header, kind, body = message -Omitting a default value leaves the variable uninitialized:: +Omitting the initial value leaves the variable uninitialized:: a: int print(a) # raises NameError @@ -201,50 +207,57 @@ in ``__init__`` or ``__new__``. The proposed syntax is as follows:: damage: int # instance variable without default stats: ClassVar[Dict[str, int]] = {} # class variable -Here ``ClassVar`` is a special class in typing module that indicates to -static type checker that this variable should not be set on instances. +Here ``ClassVar`` is a special class defined by the typing module that +indicates to the 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: captain = 'Picard' stats = {} + def __init__(self, damage, captain=None): self.damage = damage if captain: self.captain = captain # Else keep the default + def hit(self): Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1 ``stats`` is intended to be a class variable (keeping track of many different per-game statistics), while ``captain`` is an instance variable with a default -value set in the class. This difference could not be seen by type -checker -- both get initialized in the class, but ``captain`` serves only +value set in the class. This difference might not be seen by a type +checker: both get initialized in the class, but ``captain`` serves only as a convenient default value for the instance variable, while ``stats`` 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 instances. +types wrapped in ``ClassVar[...]``. In this way a type checker may flag +accidental assignments to attributes with the same name on instances. + For example, annotating the discussed class:: class Starship: captain: str = 'Picard' damage: int stats: ClassVar[Dict[str, int]] = {} + def __init__(self, damage: int, captain: str = None): self.damage = damage if captain: self.captain = captain # Else keep the default + def hit(self): Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1 - enterprise_d = Starship(3000) - enterprise_d.stats = {} # Flagged as error by a type checker - Starship.stats = {} # This is OK + enterprise_d = Starship(3000) + enterprise_d.stats = {} # Flagged as error by a type checker + Starship.stats = {} # This is OK -As a matter of convenience, instance variables can be annotated in -``__init__`` or other methods, rather than in class:: +As a matter of convenience (and convention), instance variables can be +annotated in ``__init__`` or other methods, rather than in the class:: from typing import Generic, TypeVar T = TypeVar(’T’) @@ -256,7 +269,9 @@ As a matter of convenience, instance variables can be annotated in Annotating expressions ********************** -The target of the annotation can be any valid single assignment target:: +The target of the annotation can be any valid single assignment +target, at least syntactically (it is up to the type checker what to +do with this):: class Cls: pass @@ -269,7 +284,7 @@ The target of the annotation can be any valid single assignment target:: d['a']: int = 0 # Annotates d['a'] with int. d['b']: int # Annotates d['b'] with int. -Note that even a parenthesized name considered an expression, +Note that even a parenthesized name is considered an expression, not a simple name:: (x): int # Annotates x with int, (x) treated as expression by compiler. @@ -310,7 +325,7 @@ 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 + and is used to annotate class variables that should not be set on class instances. This restriction is ensured by static checkers, but not at runtime. See the `classvar`_ section for examples and explanations for the usage of @@ -319,7 +334,7 @@ Changes to Standard Library and Documentation - 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. + and classes as well as functions. 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``) @@ -460,8 +475,7 @@ Rejected/Postponed Proposals course subjective.) - **Allow type annotations for tuple unpacking:** - This causes an ambiguity: It's not clear what meaning should be - assigned to this statement:: + This causes ambiguity: it's not clear what this statement means:: x, y: T @@ -484,8 +498,8 @@ Rejected/Postponed Proposals x: int = y = 1 z = w: int = 1 - it is ambiguous, what should be the type of ``y``, and what should - be the type of ``z``. Also the second line is difficult to parse. + it is ambiguous, what should the types of ``y`` and ``z`` be? + Also the second line is difficult to parse. - **Allow annotations in ``with`` and ``for`` statement:** This was rejected because in ``for`` it would make it hard to spot the actual @@ -523,9 +537,8 @@ Rejected/Postponed Proposals - **Use syntax** ``x: class t = v`` **for class variables:** This would require a more complicated parser and the ``class`` keyword would 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. + need to have ``ClassVar`` store class variables to + ``__annotations__``, so a simpler syntax was chosen. - **Forget about** ``ClassVar`` **altogether:** This was proposed since mypy seems to be getting along fine without a way