PEP 526 updates (#84)

This commit is contained in:
Guido van Rossum 2016-09-02 15:03:41 -07:00 committed by GitHub
parent 58c0a72235
commit e48206a7da
1 changed files with 48 additions and 35 deletions

View File

@ -8,7 +8,7 @@ Type: Standards Track
Content-Type: text/x-rst Content-Type: text/x-rst
Created: 09-Aug-2016 Created: 09-Aug-2016
Python-Version: 3.6 Python-Version: 3.6
Post-History: 30-Aug-2016 Post-History: 30-Aug-2016, 02-Sep-2016
Notice for Reviewers Notice for Reviewers
@ -21,7 +21,7 @@ There was preliminary discussion on python-ideas and at
https://github.com/python/typing/issues/258. https://github.com/python/typing/issues/258.
Before you bring up an objection in a public forum please at least 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 Abstract
@ -42,8 +42,8 @@ type comments to annotate variables::
stats = {} # type: Dict[str, int] stats = {} # type: Dict[str, int]
This PEP aims at adding syntax to Python for annotating the types of variables This PEP aims at adding syntax to Python for annotating the types of variables
(including non-method attributes), instead of expressing them through (including class variables and instance variables),
comments:: instead of expressing them through comments::
primes: List[int] = [] primes: List[int] = []
@ -52,6 +52,12 @@ comments::
class Starship: class Starship:
stats: ClassVar[Dict[str, int]] = {} 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 Rationale
========= =========
@ -72,14 +78,14 @@ expressed through comments has some downsides:
my_var = another_function() # Why isn't there a type here? 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 - 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``. ``ast``.
- Type comments are used a lot in typeshed. Migrating typeshed to use - Type comments are used a lot in typeshed. Migrating typeshed to use
the variable annotation syntax instead of type comments would improve the variable annotation syntax instead of type comments would improve
readability of stubs. 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:: difficult to distinguish them::
path = None # type: Optional[str] # Path to module source path = None # type: Optional[str] # Path to module source
@ -97,15 +103,15 @@ by PEP 484.
Non-goals Non-goals
********* *********
While the proposal is accompanied by an extension of ``typing.get_type_hints`` While the proposal is accompanied by an extension of the ``typing.get_type_hints``
standard library function for runtime retrieval of annotations, the variable standard library function for runtime retrieval of annotations, variable
annotations are not designed for runtime type checking. Third party packages 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 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, 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 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 This PEP does not require type checkers to change their type checking
rules. It merely provides a more readable syntax to replace type 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:: The types of locals and globals can be annotated as follows::
some_number: int # variable without default some_number: int # variable without initial value
some_list: List[int] = [] # variable with default some_list: List[int] = [] # variable with initial value
Being able to omit the initial value allows for easier typing of variables Being able to omit the initial value allows for easier typing of variables
assigned in conditional branches:: assigned in conditional branches::
@ -160,7 +166,7 @@ one to annotate the types of variables when tuple unpacking is used::
body: Optional[List[str]] body: Optional[List[str]]
header, kind, body = message header, kind, body = message
Omitting a default value leaves the variable uninitialized:: Omitting the initial value leaves the variable uninitialized::
a: int a: int
print(a) # raises NameError print(a) # raises NameError
@ -201,41 +207,48 @@ in ``__init__`` or ``__new__``. The proposed syntax is as follows::
damage: int # instance variable without 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 Here ``ClassVar`` is a special class defined by the typing module that
static type checker that this variable should not be set on instances. 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:: This could be illustrated with a more detailed example. In this class::
class Starship: class Starship:
captain = 'Picard' captain = 'Picard'
stats = {} stats = {}
def __init__(self, damage, captain=None): def __init__(self, damage, captain=None):
self.damage = damage self.damage = damage
if captain: if captain:
self.captain = captain # Else keep the default self.captain = captain # Else keep the default
def hit(self): def hit(self):
Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1 Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1
``stats`` is intended to be a class variable (keeping track of many different ``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 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 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 checker: both get initialized in the class, but ``captain`` serves only
as a convenient default value for the instance variable, while ``stats`` 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. 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 Since both variables happen to be initialized at the class level, it is
useful to distinguish them by marking class variables as annotated with useful to distinguish them by marking class variables as annotated with
types wrapped in ``ClassVar[...]``. In such way type checker will prevent types wrapped in ``ClassVar[...]``. In this way a type checker may flag
accidental assignments to attributes with a same name on instances. accidental assignments to attributes with the same name on instances.
For example, annotating the discussed class:: For example, annotating the discussed class::
class Starship: class Starship:
captain: str = 'Picard' captain: str = 'Picard'
damage: int damage: int
stats: ClassVar[Dict[str, int]] = {} stats: ClassVar[Dict[str, int]] = {}
def __init__(self, damage: int, captain: str = None): def __init__(self, damage: int, captain: str = None):
self.damage = damage self.damage = damage
if captain: if captain:
self.captain = captain # Else keep the default self.captain = captain # Else keep the default
def hit(self): def hit(self):
Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1 Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1
@ -243,8 +256,8 @@ For example, annotating the discussed class::
enterprise_d.stats = {} # Flagged as error by a type checker enterprise_d.stats = {} # Flagged as error by a type checker
Starship.stats = {} # This is OK Starship.stats = {} # This is OK
As a matter of convenience, instance variables can be annotated in As a matter of convenience (and convention), instance variables can be
``__init__`` or other methods, rather than in class:: annotated in ``__init__`` or other methods, rather than in the class::
from typing import Generic, TypeVar from typing import Generic, TypeVar
T = TypeVar(T) T = TypeVar(T)
@ -256,7 +269,9 @@ As a matter of convenience, instance variables can be annotated in
Annotating expressions 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: class Cls:
pass 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['a']: int = 0 # Annotates d['a'] with int.
d['b']: int # Annotates d['b'] 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:: not a simple name::
(x): int # Annotates x with int, (x) treated as expression by compiler. (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`` - 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, 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, instances. This restriction is ensured by static checkers,
but not at runtime. See the but not at runtime. See the
`classvar`_ section for examples and explanations for the usage of `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, - Function ``get_type_hints`` in the ``typing`` module will be extended,
so that one can retrieve type annotations at runtime from modules 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 Annotations are returned as a dictionary mapping from variable or arguments
to their type hints with forward references evaluated. to their type hints with forward references evaluated.
For classes it returns a mapping (perhaps ``collections.ChainMap``) For classes it returns a mapping (perhaps ``collections.ChainMap``)
@ -460,8 +475,7 @@ Rejected/Postponed Proposals
course subjective.) course subjective.)
- **Allow type annotations for tuple unpacking:** - **Allow type annotations for tuple unpacking:**
This causes an ambiguity: It's not clear what meaning should be This causes ambiguity: it's not clear what this statement means::
assigned to this statement::
x, y: T x, y: T
@ -484,8 +498,8 @@ Rejected/Postponed Proposals
x: int = y = 1 x: int = y = 1
z = w: int = 1 z = w: int = 1
it is ambiguous, what should be the type of ``y``, and what should it is ambiguous, what should the types of ``y`` and ``z`` be?
be the type of ``z``. Also the second line is difficult to parse. Also the second line is difficult to parse.
- **Allow annotations in ``with`` and ``for`` statement:** - **Allow annotations in ``with`` and ``for`` statement:**
This was rejected because in ``for`` it would make it hard to spot the actual 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:** - **Use syntax** ``x: class t = v`` **for class variables:**
This would require a more complicated parser and the ``class`` This would require a more complicated parser and the ``class``
keyword would confuse simple-minded syntax highlighters. Anyway we keyword would confuse simple-minded syntax highlighters. Anyway we
need to have ``ClassVar`` to store class variables to need to have ``ClassVar`` store class variables to
``__annotations__``, so that it was decided to go with a simpler ``__annotations__``, so a simpler syntax was chosen.
syntax.
- **Forget about** ``ClassVar`` **altogether:** - **Forget about** ``ClassVar`` **altogether:**
This was proposed since mypy seems to be getting along fine without a way This was proposed since mypy seems to be getting along fine without a way