222 lines
6.4 KiB
Plaintext
222 lines
6.4 KiB
Plaintext
PEP: 526
|
|
Title: Syntax for Variable and Attribute Annotations
|
|
Version: $Revision$
|
|
Last-Modified: $Date$
|
|
Author: Ryan Gonzalez <rymg19@gmail.com>, Philip House <phouse512@gmail.com>, Guido van Rossum <guido@python.org>
|
|
Status: Draft
|
|
Type: Standards Track
|
|
Content-Type: text/x-rst
|
|
Created: 09-Aug-2016
|
|
Python-Version: 3.6
|
|
|
|
Notice for Reviewers
|
|
====================
|
|
|
|
This PEP is not ready for review. We're merely committing changes
|
|
frequently so we don't end up with a huge merge conflict. For minor
|
|
textual nits please use https://github.com/python/peps/pull/72. For
|
|
discussion about contents, please refer to
|
|
https://github.com/python/typing/issues/258 (but please be patient, we
|
|
know we're way behind addressing all comments).
|
|
|
|
Abstract
|
|
========
|
|
|
|
PEP 484 introduced type hints and; In particular, it introduced the notion of
|
|
type comments::
|
|
|
|
# a is specified to be a list of ints.
|
|
a = [] # type: List[int]
|
|
# b is a string
|
|
b = None # type: str
|
|
class Cls:
|
|
my_class_attr = True # type: bool
|
|
|
|
This PEP aims at adding syntax to Python for annotating the types of variables and
|
|
attributes, instead of expressing them through comments::
|
|
|
|
a: List[int] = []
|
|
b: str
|
|
class Cls:
|
|
my_class_attr: ClassAttr[bool] = True
|
|
|
|
Rationale
|
|
=========
|
|
|
|
Although type comments work well, the fact that they're expressed through
|
|
comments has some downsides:
|
|
|
|
- Text editors often highlight comments differently from type annotations.
|
|
- There isn't a way to annotate the type of an undefined variable; you need to
|
|
initialize it to ``None`` (e.g. ``a = None # type: int``).
|
|
- Variables annotated in a conditional branch are difficult to read::
|
|
|
|
if some_value:
|
|
my_var = function() # type: Logger
|
|
else:
|
|
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
|
|
``ast``.
|
|
- It's impossible to retrieve the annotations at runtime outside of attempting to
|
|
find the module's source code and parse it at runtime, 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.
|
|
|
|
Specification
|
|
=============
|
|
|
|
*** big key concepts, not quite sure what the best way to organize this would be,
|
|
or if they deserve their own sections ***
|
|
|
|
Normal Variable Typing
|
|
**********************
|
|
|
|
The types of locals and globals can be annotated as follows::
|
|
|
|
some_number: int # variable without default
|
|
some_list: List[int] = [] # variable with default
|
|
|
|
Being able to omit the initial value allows for easier typing of variables
|
|
assigned in conditional branches::
|
|
|
|
sane_world: bool
|
|
if 2+2 == 4:
|
|
sane_world = True
|
|
else:
|
|
sane_world = False
|
|
|
|
Note that, although this 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
|
|
t: Any = (1, 2, 3)
|
|
|
|
# Tuple unpacking with type comments
|
|
x, y, z = t # type: int, int, int
|
|
|
|
# Tuple unpacking with variable annotation syntax
|
|
x: int
|
|
y: int
|
|
z: int
|
|
x, y, z = t
|
|
|
|
Omitting a default value leaves the variable uninitialized::
|
|
|
|
a: int
|
|
print(a) # raises NameError
|
|
|
|
However, annotating a local variable will cause the interpreter to always make
|
|
it a local::
|
|
|
|
def f():
|
|
a: int
|
|
print(a) # raises UnboundLocalError
|
|
# Commenting out the `a: int` makes it a NameError!
|
|
|
|
as if the code were::
|
|
|
|
def f():
|
|
if False: a = 0
|
|
print(a) # raises UnboundLocalError
|
|
|
|
|
|
Class Variable Typing
|
|
*********************
|
|
|
|
Adding variable types allow for us annotate the types of instance variables 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 looks as follows::
|
|
|
|
class Starship:
|
|
captain: str # instance variable without default
|
|
damage: int = 0 # instance variable with default
|
|
stats: class Dict[str, int] = {} # class variable with initialization
|
|
|
|
|
|
Duplicate annotations
|
|
*********************
|
|
|
|
Any duplicate type annotations will be ignored::
|
|
|
|
a: int
|
|
a: int # Doesn't do anything.
|
|
|
|
The Python compiler will not validate the type expression, and leave it to
|
|
the type checker to complain. The above code will be allowed by the
|
|
compiler at runtime.
|
|
|
|
Where annotations aren't allowed
|
|
********************************
|
|
|
|
It's illegal to attempt to annotate ``global`` and ``nonlocal``::
|
|
|
|
def f():
|
|
global x: int # SyntaxError
|
|
|
|
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``
|
|
statement; they must be annotated ahead of time, in a similar manner to tuple
|
|
unpacking::
|
|
|
|
a: int
|
|
for a in my_iter:
|
|
f: MyFile
|
|
with myfunc() as f:
|
|
# ...
|
|
|
|
Capturing Types at Runtime
|
|
**************************
|
|
|
|
In order to capture variable types that are usable at runtime, we store the
|
|
types in `__annotations__` as dictionaries at various levels. At each level (for
|
|
example, global), the types dictionary would be stored in the `__annotations__`
|
|
dictionary for that given level. Here is an example for both global and class
|
|
level types::
|
|
|
|
# print global type annotations
|
|
players: Dict[str, Player]
|
|
print(__annotations__)
|
|
|
|
# print class type annotations
|
|
class Starship:
|
|
hitpoints: class int = 50
|
|
stats: class Dict[str, int] = {}
|
|
shield: int = 100
|
|
captain: str # no initial value
|
|
print(Starship.__annotations__)
|
|
|
|
A note about locals -- the value of having annotations available locally does not
|
|
offset the cost of having to create and populate the annotations dictionary on
|
|
every function call.
|
|
|
|
These annotations would be printed out from the previous program as follows::
|
|
|
|
{'players': Dict[str, Player]}
|
|
|
|
{'hitpoints': ClassVar[int],
|
|
'stats': ClassVar[Dict[str, int]],
|
|
'shield': int,
|
|
'captain': str
|
|
}
|
|
|
|
Mypy supports allowing `# type` on assignments to instance variables and other things.
|
|
In case you prefer annotating instance variables in `__init__` or `__new__`, you can
|
|
also annotate variable types for instance variables in methods. Despite this,
|
|
`__annotations__` will not be updated for that class.
|
|
|
|
Backwards Compatibility
|
|
=======================
|
|
|
|
|
|
Copyright
|
|
=========
|
|
|
|
This document has been placed in the public domain.
|