690 lines
27 KiB
ReStructuredText
690 lines
27 KiB
ReStructuredText
PEP: 563
|
|
Title: Postponed Evaluation of Annotations
|
|
Version: $Revision$
|
|
Last-Modified: $Date$
|
|
Author: Łukasz Langa <lukasz@python.org>
|
|
Discussions-To: python-dev@python.org
|
|
Status: Accepted
|
|
Type: Standards Track
|
|
Topic: Typing
|
|
Content-Type: text/x-rst
|
|
Created: 08-Sep-2017
|
|
Python-Version: 3.7
|
|
Post-History: 01-Nov-2017, 21-Nov-2017
|
|
Resolution: https://mail.python.org/pipermail/python-dev/2017-December/151042.html
|
|
|
|
|
|
Abstract
|
|
========
|
|
|
|
:pep:`3107` introduced syntax for function annotations, but the semantics
|
|
were deliberately left undefined. :pep:`484` introduced a standard meaning
|
|
to annotations: type hints. :pep:`526` defined variable annotations,
|
|
explicitly tying them with the type hinting use case.
|
|
|
|
This PEP proposes changing function annotations and variable annotations
|
|
so that they are no longer evaluated at function definition time.
|
|
Instead, they are preserved in ``__annotations__`` in string form.
|
|
|
|
This change is being introduced gradually, starting with a
|
|
``__future__`` import in Python 3.7.
|
|
|
|
|
|
Rationale and Goals
|
|
===================
|
|
|
|
:pep:`3107` added support for arbitrary annotations on parts of a function
|
|
definition. Just like default values, annotations are evaluated at
|
|
function definition time. This creates a number of issues for the type
|
|
hinting use case:
|
|
|
|
* forward references: when a type hint contains names that have not been
|
|
defined yet, that definition needs to be expressed as a string
|
|
literal;
|
|
|
|
* type hints are executed at module import time, which is not
|
|
computationally free.
|
|
|
|
Postponing the evaluation of annotations solves both problems.
|
|
NOTE: :pep:`649` proposes an alternative solution to the above issues,
|
|
putting this PEP in danger of being superseded.
|
|
|
|
Non-goals
|
|
---------
|
|
|
|
Just like in :pep:`484` and :pep:`526`, it should 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.**
|
|
|
|
This PEP is meant to solve the problem of forward references in type
|
|
annotations. There are still cases outside of annotations where
|
|
forward references will require usage of string literals. Those are
|
|
listed in a later section of this document.
|
|
|
|
Annotations without forced evaluation enable opportunities to improve
|
|
the syntax of type hints. This idea will require its own separate PEP
|
|
and is not discussed further in this document.
|
|
|
|
Non-typing usage of annotations
|
|
-------------------------------
|
|
|
|
While annotations are still available for arbitrary use besides type
|
|
checking, it is worth mentioning that the design of this PEP, as well
|
|
as its precursors (:pep:`484` and :pep:`526`), is predominantly motivated by
|
|
the type hinting use case.
|
|
|
|
In Python 3.8 :pep:`484` will graduate from provisional status. Other
|
|
enhancements to the Python programming language like :pep:`544`, :pep:`557`,
|
|
or :pep:`560`, are already being built on this basis as they depend on
|
|
type annotations and the ``typing`` module as defined by :pep:`484`.
|
|
In fact, the reason :pep:`484` is staying provisional in Python 3.7 is to
|
|
enable rapid evolution for another release cycle that some of the
|
|
aforementioned enhancements require.
|
|
|
|
With this in mind, uses for annotations incompatible with the
|
|
aforementioned PEPs should be considered deprecated.
|
|
|
|
|
|
Implementation
|
|
==============
|
|
|
|
With this PEP, function and variable annotations will no longer be
|
|
evaluated at definition time. Instead, a string form will be preserved
|
|
in the respective ``__annotations__`` dictionary. Static type checkers
|
|
will see no difference in behavior, whereas tools using annotations at
|
|
runtime will have to perform postponed evaluation.
|
|
|
|
The string form is obtained from the AST during the compilation step,
|
|
which means that the string form might not preserve the exact formatting
|
|
of the source. Note: if an annotation was a string literal already, it
|
|
will still be wrapped in a string.
|
|
|
|
Annotations need to be syntactically valid Python expressions, also when
|
|
passed as literal strings (i.e. ``compile(literal, '', 'eval')``).
|
|
Annotations can only use names present in the module scope as postponed
|
|
evaluation using local names is not reliable (with the sole exception of
|
|
class-level names resolved by ``typing.get_type_hints()``).
|
|
|
|
Note that as per :pep:`526`, local variable annotations are not evaluated
|
|
at all since they are not accessible outside of the function's closure.
|
|
|
|
Enabling the future behavior in Python 3.7
|
|
------------------------------------------
|
|
|
|
The functionality described above can be enabled starting from Python
|
|
3.7 using the following special import::
|
|
|
|
from __future__ import annotations
|
|
|
|
A reference implementation of this functionality is available
|
|
`on GitHub <https://github.com/python/cpython/pull/4390>`_.
|
|
|
|
|
|
Resolving Type Hints at Runtime
|
|
===============================
|
|
|
|
To resolve an annotation at runtime from its string form to the result
|
|
of the enclosed expression, user code needs to evaluate the string.
|
|
|
|
For code that uses type hints, the
|
|
``typing.get_type_hints(obj, globalns=None, localns=None)`` function
|
|
correctly evaluates expressions back from its string form. Note that
|
|
all valid code currently using ``__annotations__`` should already be
|
|
doing that since a type annotation can be expressed as a string literal.
|
|
|
|
For code which uses annotations for other purposes, a regular
|
|
``eval(ann, globals, locals)`` call is enough to resolve the
|
|
annotation.
|
|
|
|
In both cases it's important to consider how globals and locals affect
|
|
the postponed evaluation. An annotation is no longer evaluated at the
|
|
time of definition and, more importantly, *in the same scope* where it
|
|
was defined. Consequently, using local state in annotations is no
|
|
longer possible in general. As for globals, the module where the
|
|
annotation was defined is the correct context for postponed evaluation.
|
|
|
|
The ``get_type_hints()`` function automatically resolves the correct
|
|
value of ``globalns`` for functions and classes. It also automatically
|
|
provides the correct ``localns`` for classes.
|
|
|
|
When running ``eval()``,
|
|
the value of globals can be gathered in the following way:
|
|
|
|
* function objects hold a reference to their respective globals in an
|
|
attribute called ``__globals__``;
|
|
|
|
* classes hold the name of the module they were defined in, this can be
|
|
used to retrieve the respective globals::
|
|
|
|
cls_globals = vars(sys.modules[SomeClass.__module__])
|
|
|
|
Note that this needs to be repeated for base classes to evaluate all
|
|
``__annotations__``.
|
|
|
|
* modules should use their own ``__dict__``.
|
|
|
|
The value of ``localns`` cannot be reliably retrieved for functions
|
|
because in all likelihood the stack frame at the time of the call no
|
|
longer exists.
|
|
|
|
For classes, ``localns`` can be composed by chaining vars of the given
|
|
class and its base classes (in the method resolution order). Since slots
|
|
can only be filled after the class was defined, we don't need to consult
|
|
them for this purpose.
|
|
|
|
Runtime annotation resolution and class decorators
|
|
--------------------------------------------------
|
|
|
|
Metaclasses and class decorators that need to resolve annotations for
|
|
the current class will fail for annotations that use the name of the
|
|
current class. Example::
|
|
|
|
def class_decorator(cls):
|
|
annotations = get_type_hints(cls) # raises NameError on 'C'
|
|
print(f'Annotations for {cls}: {annotations}')
|
|
return cls
|
|
|
|
@class_decorator
|
|
class C:
|
|
singleton: 'C' = None
|
|
|
|
This was already true before this PEP. The class decorator acts on
|
|
the class before it's assigned a name in the current definition scope.
|
|
|
|
Runtime annotation resolution and ``TYPE_CHECKING``
|
|
---------------------------------------------------
|
|
|
|
Sometimes there's code that must be seen by a type checker but should
|
|
not be executed. For such situations the ``typing`` module defines a
|
|
constant, ``TYPE_CHECKING``, that is considered ``True`` during type
|
|
checking but ``False`` at runtime. Example::
|
|
|
|
import typing
|
|
|
|
if typing.TYPE_CHECKING:
|
|
import expensive_mod
|
|
|
|
def a_func(arg: expensive_mod.SomeClass) -> None:
|
|
a_var: expensive_mod.SomeClass = arg
|
|
...
|
|
|
|
This approach is also useful when handling import cycles.
|
|
|
|
Trying to resolve annotations of ``a_func`` at runtime using
|
|
``typing.get_type_hints()`` will fail since the name ``expensive_mod``
|
|
is not defined (``TYPE_CHECKING`` variable being ``False`` at runtime).
|
|
This was already true before this PEP.
|
|
|
|
|
|
Backwards Compatibility
|
|
=======================
|
|
|
|
This is a backwards incompatible change. Applications depending on
|
|
arbitrary objects to be directly present in annotations will break
|
|
if they are not using ``typing.get_type_hints()`` or ``eval()``.
|
|
|
|
Annotations that depend on locals at the time of the function
|
|
definition will not be resolvable later. Example::
|
|
|
|
def generate():
|
|
A = Optional[int]
|
|
class C:
|
|
field: A = 1
|
|
def method(self, arg: A) -> None: ...
|
|
return C
|
|
X = generate()
|
|
|
|
Trying to resolve annotations of ``X`` later by using
|
|
``get_type_hints(X)`` will fail because ``A`` and its enclosing scope no
|
|
longer exists. Python will make no attempt to disallow such annotations
|
|
since they can often still be successfully statically analyzed, which is
|
|
the predominant use case for annotations.
|
|
|
|
Annotations using nested classes and their respective state are still
|
|
valid. They can use local names or the fully qualified name. Example::
|
|
|
|
class C:
|
|
field = 'c_field'
|
|
def method(self) -> C.field: # this is OK
|
|
...
|
|
|
|
def method(self) -> field: # this is OK
|
|
...
|
|
|
|
def method(self) -> C.D: # this is OK
|
|
...
|
|
|
|
def method(self) -> D: # this is OK
|
|
...
|
|
|
|
class D:
|
|
field2 = 'd_field'
|
|
def method(self) -> C.D.field2: # this is OK
|
|
...
|
|
|
|
def method(self) -> D.field2: # this FAILS, class D is local to C
|
|
... # and is therefore only available
|
|
# as C.D. This was already true
|
|
# before the PEP.
|
|
|
|
def method(self) -> field2: # this is OK
|
|
...
|
|
|
|
def method(self) -> field: # this FAILS, field is local to C and
|
|
# is therefore not visible to D unless
|
|
# accessed as C.field. This was already
|
|
# true before the PEP.
|
|
|
|
In the presence of an annotation that isn't a syntactically valid
|
|
expression, SyntaxError is raised at compile time. However, since names
|
|
aren't resolved at that time, no attempt is made to validate whether
|
|
used names are correct or not.
|
|
|
|
Deprecation policy
|
|
------------------
|
|
|
|
Starting with Python 3.7, a ``__future__`` import is required to use the
|
|
described functionality. No warnings are raised.
|
|
|
|
NOTE: Whether this will eventually become the default behavior is currently unclear
|
|
pending decision on :pep:`649`. In any case, use of annotations that depend upon
|
|
their eager evaluation is incompatible with both proposals and is no longer
|
|
supported.
|
|
|
|
Forward References
|
|
==================
|
|
|
|
Deliberately using a name before it was defined in the module is called
|
|
a forward reference. For the purpose of this section, we'll call
|
|
any name imported or defined within a ``if TYPE_CHECKING:`` block
|
|
a forward reference, too.
|
|
|
|
This PEP addresses the issue of forward references in *type annotations*.
|
|
The use of string literals will no longer be required in this case.
|
|
However, there are APIs in the ``typing`` module that use other syntactic
|
|
constructs of the language, and those will still require working around
|
|
forward references with string literals. The list includes:
|
|
|
|
* type definitions::
|
|
|
|
T = TypeVar('T', bound='<type>')
|
|
UserId = NewType('UserId', '<type>')
|
|
Employee = NamedTuple('Employee', [('name', '<type>'), ('id', '<type>')])
|
|
|
|
* aliases::
|
|
|
|
Alias = Optional['<type>']
|
|
AnotherAlias = Union['<type>', '<type>']
|
|
YetAnotherAlias = '<type>'
|
|
|
|
* casting::
|
|
|
|
cast('<type>', value)
|
|
|
|
* base classes::
|
|
|
|
class C(Tuple['<type>', '<type>']): ...
|
|
|
|
Depending on the specific case, some of the cases listed above might be
|
|
worked around by placing the usage in a ``if TYPE_CHECKING:`` block.
|
|
This will not work for any code that needs to be available at runtime,
|
|
notably for base classes and casting. For named tuples, using the new
|
|
class definition syntax introduced in Python 3.6 solves the issue.
|
|
|
|
In general, fixing the issue for *all* forward references requires
|
|
changing how module instantiation is performed in Python, from the
|
|
current single-pass top-down model. This would be a major change in the
|
|
language and is out of scope for this PEP.
|
|
|
|
|
|
Rejected Ideas
|
|
==============
|
|
|
|
Keeping the ability to use function local state when defining annotations
|
|
-------------------------------------------------------------------------
|
|
|
|
With postponed evaluation, this would require keeping a reference to
|
|
the frame in which an annotation got created. This could be achieved
|
|
for example by storing all annotations as lambdas instead of strings.
|
|
|
|
This would be prohibitively expensive for highly annotated code as the
|
|
frames would keep all their objects alive. That includes predominantly
|
|
objects that won't ever be accessed again.
|
|
|
|
To be able to address class-level scope, the lambda approach would
|
|
require a new kind of cell in the interpreter. This would proliferate
|
|
the number of types that can appear in ``__annotations__``, as well as
|
|
wouldn't be as introspectable as strings.
|
|
|
|
Note that in the case of nested classes, the functionality to get the
|
|
effective "globals" and "locals" at definition time is provided by
|
|
``typing.get_type_hints()``.
|
|
|
|
If a function generates a class or a function with annotations that
|
|
have to use local variables, it can populate the given generated
|
|
object's ``__annotations__`` dictionary directly, without relying on
|
|
the compiler.
|
|
|
|
Disallowing local state usage for classes, too
|
|
----------------------------------------------
|
|
|
|
This PEP originally proposed limiting names within annotations to only
|
|
allow names from the model-level scope, including for classes. The
|
|
author argued this makes name resolution unambiguous, including in cases
|
|
of conflicts between local names and module-level names.
|
|
|
|
This idea was ultimately rejected in case of classes. Instead,
|
|
``typing.get_type_hints()`` got modified to populate the local namespace
|
|
correctly if class-level annotations are needed.
|
|
|
|
The reasons for rejecting the idea were that it goes against the
|
|
intuition of how scoping works in Python, and would break enough
|
|
existing type annotations to make the transition cumbersome. Finally,
|
|
local scope access is required for class decorators to be able to
|
|
evaluate type annotations. This is because class decorators are applied
|
|
before the class receives its name in the outer scope.
|
|
|
|
Introducing a new dictionary for the string literal form instead
|
|
----------------------------------------------------------------
|
|
|
|
Yury Selivanov shared the following idea:
|
|
|
|
1. Add a new special attribute to functions: ``__annotations_text__``.
|
|
|
|
2. Make ``__annotations__`` a lazy dynamic mapping, evaluating
|
|
expressions from the corresponding key in ``__annotations_text__``
|
|
just-in-time.
|
|
|
|
This idea is supposed to solve the backwards compatibility issue,
|
|
removing the need for a new ``__future__`` import. Sadly, this is not
|
|
enough. Postponed evaluation changes which state the annotation has
|
|
access to. While postponed evaluation fixes the forward reference
|
|
problem, it also makes it impossible to access function-level locals
|
|
anymore. This alone is a source of backwards incompatibility which
|
|
justifies a deprecation period.
|
|
|
|
A ``__future__`` import is an obvious and explicit indicator of opting
|
|
in for the new functionality. It also makes it trivial for external
|
|
tools to recognize the difference between a Python files using the old
|
|
or the new approach. In the former case, that tool would recognize that
|
|
local state access is allowed, whereas in the latter case it would
|
|
recognize that forward references are allowed.
|
|
|
|
Finally, just-in-time evaluation in ``__annotations__`` is an
|
|
unnecessary step if ``get_type_hints()`` is used later.
|
|
|
|
Dropping annotations with -O
|
|
----------------------------
|
|
|
|
There are two reasons this is not satisfying for the purpose of this
|
|
PEP.
|
|
|
|
First, this only addresses runtime cost, not forward references, those
|
|
still cannot be safely used in source code. A library maintainer would
|
|
never be able to use forward references since that would force the
|
|
library users to use this new hypothetical -O switch.
|
|
|
|
Second, this throws the baby out with the bath water. Now *no* runtime
|
|
annotation use can be performed. :pep:`557` is one example of a recent
|
|
development where evaluating type annotations at runtime is useful.
|
|
|
|
All that being said, a granular -O option to drop annotations is
|
|
a possibility in the future, as it's conceptually compatible with
|
|
existing -O behavior (dropping docstrings and assert statements). This
|
|
PEP does not invalidate the idea.
|
|
|
|
Passing string literals in annotations verbatim to ``__annotations__``
|
|
----------------------------------------------------------------------
|
|
|
|
This PEP originally suggested directly storing the contents of a string
|
|
literal under its respective key in ``__annotations__``. This was
|
|
meant to simplify support for runtime type checkers.
|
|
|
|
Mark Shannon pointed out this idea was flawed since it wasn't handling
|
|
situations where strings are only part of a type annotation.
|
|
|
|
The inconsistency of it was always apparent but given that it doesn't
|
|
fully prevent cases of double-wrapping strings anyway, it is not worth
|
|
it.
|
|
|
|
Making the name of the future import more verbose
|
|
-------------------------------------------------
|
|
|
|
Instead of requiring the following import::
|
|
|
|
from __future__ import annotations
|
|
|
|
the PEP could call the feature more explicitly, for example
|
|
``string_annotations``, ``stringify_annotations``,
|
|
``annotation_strings``, ``annotations_as_strings``, ``lazy_annotations``,
|
|
``static_annotations``, etc.
|
|
|
|
The problem with those names is that they are very verbose. Each of
|
|
them besides ``lazy_annotations`` would constitute the longest future
|
|
feature name in Python. They are long to type and harder to remember
|
|
than the single-word form.
|
|
|
|
There is precedence of a future import name that sounds overly generic
|
|
but in practice was obvious to users as to what it does::
|
|
|
|
from __future__ import division
|
|
|
|
|
|
Prior discussion
|
|
================
|
|
|
|
In PEP 484
|
|
----------
|
|
|
|
The forward reference problem was discussed when :pep:`484` was originally
|
|
drafted, leading to the following statement in the document:
|
|
|
|
A compromise is possible where a ``__future__`` import could enable
|
|
turning *all* annotations in a given module into string literals, as
|
|
follows::
|
|
|
|
from __future__ import annotations
|
|
|
|
class ImSet:
|
|
def add(self, a: ImSet) -> List[ImSet]: ...
|
|
|
|
assert ImSet.add.__annotations__ == {
|
|
'a': 'ImSet', 'return': 'List[ImSet]'
|
|
}
|
|
|
|
Such a ``__future__`` import statement may be proposed in a separate
|
|
PEP.
|
|
|
|
python/typing#400
|
|
-----------------
|
|
|
|
The problem was discussed at length on the typing module's GitHub
|
|
project, under `Issue 400 <https://github.com/python/typing/issues/400>`_.
|
|
The problem statement there includes critique of generic types requiring
|
|
imports from ``typing``. This tends to be confusing to
|
|
beginners:
|
|
|
|
Why this::
|
|
|
|
from typing import List, Set
|
|
def dir(o: object = ...) -> List[str]: ...
|
|
def add_friends(friends: Set[Friend]) -> None: ...
|
|
|
|
But not this::
|
|
|
|
def dir(o: object = ...) -> list[str]: ...
|
|
def add_friends(friends: set[Friend]) -> None ...
|
|
|
|
Why this::
|
|
|
|
up_to_ten = list(range(10))
|
|
friends = set()
|
|
|
|
But not this::
|
|
|
|
from typing import List, Set
|
|
up_to_ten = List[int](range(10))
|
|
friends = Set[Friend]()
|
|
|
|
While typing usability is an interesting problem, it is out of scope
|
|
of this PEP. Specifically, any extensions of the typing syntax
|
|
standardized in :pep:`484` will require their own respective PEPs and
|
|
approval.
|
|
|
|
Issue 400 ultimately suggests postponing evaluation of annotations and
|
|
keeping them as strings in ``__annotations__``, just like this PEP
|
|
specifies. This idea was received well. Ivan Levkivskyi supported
|
|
using the ``__future__`` import and suggested unparsing the AST in
|
|
``compile.c``. Jukka Lehtosalo pointed out that there are some cases
|
|
of forward references where types are used outside of annotations and
|
|
postponed evaluation will not help those. For those cases using the
|
|
string literal notation would still be required. Those cases are
|
|
discussed briefly in the "Forward References" section of this PEP.
|
|
|
|
The biggest controversy on the issue was Guido van Rossum's concern
|
|
that untokenizing annotation expressions back to their string form has
|
|
no precedent in the Python programming language and feels like a hacky
|
|
workaround. He said:
|
|
|
|
One thing that comes to mind is that it's a very random change to
|
|
the language. It might be useful to have a more compact way to
|
|
indicate deferred execution of expressions (using less syntax than
|
|
``lambda:``). But why would the use case of type annotations be so
|
|
all-important to change the language to do it there first (rather
|
|
than proposing a more general solution), given that there's already
|
|
a solution for this particular use case that requires very minimal
|
|
syntax?
|
|
|
|
Eventually, Ethan Smith and schollii voiced that feedback gathered
|
|
during PyCon US suggests that the state of forward references needs
|
|
fixing. Guido van Rossum suggested coming back to the ``__future__``
|
|
idea, pointing out that to prevent abuse, it's important for the
|
|
annotations to be kept both syntactically valid and evaluating correctly
|
|
at runtime.
|
|
|
|
First draft discussion on python-ideas
|
|
--------------------------------------
|
|
|
|
Discussion happened largely in two threads, `the original announcement
|
|
<https://mail.python.org/pipermail/python-ideas/2017-September/thread.html#47031>`_
|
|
and a follow-up called `PEP 563 and expensive backwards compatibility
|
|
<https://mail.python.org/pipermail/python-ideas/2017-September/thread.html#47108>`_.
|
|
|
|
The PEP received rather warm feedback (4 strongly in favor,
|
|
2 in favor with concerns, 2 against). The biggest voice of concern on
|
|
the former thread being Steven D'Aprano's review stating that the
|
|
problem definition of the PEP doesn't justify breaking backwards
|
|
compatibility. In this response Steven seemed mostly concerned about
|
|
Python no longer supporting evaluation of annotations that depended on
|
|
local function/class state.
|
|
|
|
A few people voiced concerns that there are libraries using annotations
|
|
for non-typing purposes. However, none of the named libraries would be
|
|
invalidated by this PEP. They do require adapting to the new
|
|
requirement to call ``eval()`` on the annotation with the correct
|
|
``globals`` and ``locals`` set.
|
|
|
|
This detail about ``globals`` and ``locals`` having to be correct was
|
|
picked up by a number of commenters. Nick Coghlan benchmarked turning
|
|
annotations into lambdas instead of strings, sadly this proved to be
|
|
much slower at runtime than the current situation.
|
|
|
|
The latter thread was started by Jim J. Jewett who stressed that
|
|
the ability to properly evaluate annotations is an important requirement
|
|
and backwards compatibility in that regard is valuable. After some
|
|
discussion he admitted that side effects in annotations are a code smell
|
|
and modal support to either perform or not perform evaluation is
|
|
a messy solution. His biggest concern remained loss of functionality
|
|
stemming from the evaluation restrictions on global and local scope.
|
|
|
|
Nick Coghlan pointed out that some of those evaluation restrictions from
|
|
the PEP could be lifted by a clever implementation of an evaluation
|
|
helper, which could solve self-referencing classes even in the form of a
|
|
class decorator. He suggested the PEP should provide this helper
|
|
function in the standard library.
|
|
|
|
Second draft discussion on python-dev
|
|
-------------------------------------
|
|
|
|
Discussion happened mainly in the `announcement thread <https://mail.python.org/pipermail/python-dev/2017-November/150062.html>`_,
|
|
followed by a brief discussion under `Mark Shannon's post
|
|
<https://mail.python.org/pipermail/python-dev/2017-November/150637.html>`_.
|
|
|
|
Steven D'Aprano was concerned whether it's acceptable for typos to be
|
|
allowed in annotations after the change proposed by the PEP. Brett
|
|
Cannon responded that type checkers and other static analyzers (like
|
|
linters or programming text editors) will catch this type of error.
|
|
Jukka Lehtosalo added that this situation is analogous to how names in
|
|
function bodies are not resolved until the function is called.
|
|
|
|
A major topic of discussion was Nick Coghlan's suggestion to store
|
|
annotations in "thunk form", in other words as a specialized lambda
|
|
which would be able to access class-level scope (and allow for scope
|
|
customization at call time). He presented a possible design for it
|
|
(`indirect attribute cells
|
|
<https://mail.python.org/pipermail/python-dev/2017-November/150141.html>`_).
|
|
This was later seen as equivalent to "special forms" in Lisp. Guido van
|
|
Rossum expressed worry that this sort of feature cannot be safely
|
|
implemented in twelve weeks (i.e. in time before the Python 3.7 beta
|
|
freeze).
|
|
|
|
After a while it became clear that the point of division between
|
|
supporters of the string form vs. supporters of the thunk form is
|
|
actually about whether annotations should be perceived as a general
|
|
syntactic element vs. something tied to the type checking use case.
|
|
|
|
Finally, Guido van Rossum declared he's rejecting the thunk idea
|
|
based on the fact that it would require a new building block in the
|
|
interpreter. This block would be exposed in annotations, multiplying
|
|
possible types of values stored in ``__annotations__`` (arbitrary
|
|
objects, strings, and now thunks). Moreover, thunks aren't as
|
|
introspectable as strings. Most importantly, Guido van Rossum
|
|
explicitly stated interest in gradually restricting the use of
|
|
annotations to static typing (with an optional runtime component).
|
|
|
|
Nick Coghlan got convinced to :pep:`563`, too, promptly beginning
|
|
the mandatory bike shedding session on the name of the ``__future__``
|
|
import. Many debaters agreed that ``annotations`` seems like
|
|
an overly broad name for the feature name. Guido van Rossum briefly
|
|
decided to call it ``string_annotations`` but then changed his mind,
|
|
arguing that ``division`` is a precedent of a broad name with a clear
|
|
meaning.
|
|
|
|
The final improvement to the PEP suggested in the discussion by Mark
|
|
Shannon was the rejection of the temptation to pass string literals
|
|
through to ``__annotations__`` verbatim.
|
|
|
|
A side-thread of discussion started around the runtime penalty of
|
|
static typing, with topic like the import time of the ``typing``
|
|
module (which is comparable to ``re`` without dependencies, and
|
|
three times as heavy as ``re`` when counting dependencies).
|
|
|
|
|
|
Acknowledgements
|
|
================
|
|
|
|
This document could not be completed without valuable input,
|
|
encouragement and advice from Guido van Rossum, Jukka Lehtosalo, and
|
|
Ivan Levkivskyi.
|
|
|
|
The implementation was thoroughly reviewed by Serhiy Storchaka who
|
|
found all sorts of issues, including bugs, bad readability, and
|
|
performance problems.
|
|
|
|
|
|
Copyright
|
|
=========
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
..
|
|
Local Variables:
|
|
mode: indented-text
|
|
indent-tabs-mode: nil
|
|
sentence-end-double-space: t
|
|
fill-column: 70
|
|
coding: utf-8
|
|
End:
|