Manually merge changes to PEP 526.

This commit is contained in:
Guido van Rossum 2016-09-02 11:45:59 -07:00
parent 9d7598135d
commit 58c0a72235
1 changed files with 75 additions and 63 deletions

View File

@ -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 <rymg19@gmail.com>, Philip House <phouse512@gmail.com>, Ivan Levkivskyi <levkivskyi@gmail.com>, Lisa Roach <lisaroach14@gmail.com>, Guido van Rossum <guido@python.org>
@ -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.