One thing I want to point out: there are a lot of really useful Python libraries that have come to rely on annotations being objects, ranging from plac to fbuild to many others. I could understand something that delays the evaluation of annotations until they are accessed, but this seems really extreme.
On Mon, Sep 11, 2017 at 10:58 AM, Lukasz Langa <luk...@langa.pl> wrote: > PEP: 563 > Title: Postponed Evaluation of Annotations > Version: $Revision$ > Last-Modified: $Date$ > Author: Łukasz Langa <luk...@langa.pl> > Discussions-To: Python-Dev <python-...@python.org> > Status: Draft > Type: Standards Track > Content-Type: text/x-rst > Created: 8-Sep-2017 > Python-Version: 3.7 > Post-History: > Resolution: > > > 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 going to be introduced gradually, starting with a new > ``__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. > > 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.** > > Annotations are still available for arbitrary use besides type checking. > Using ``@typing.no_type_hints`` in this case is recommended to > disambiguate the use case. > > > Implementation > ============== > > In a future version of Python, 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. > > If an annotation was already a string, this string is preserved > verbatim. In other cases, the string form is obtained from the AST > during the compilation step, which means that the string form preserved > might not preserve the exact formatting of the source. > > 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. > > 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 > > > 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()`` 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. The trick here is to get the correct value for globals. > Fortunately, in the case of functions, they hold a reference to globals > in an attribute called ``__globals__``. To get the correct module-level > context to resolve class variables, use:: > > cls_globals = sys.modules[SomeClass.__module__].__dict__ > > 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. > > The situation is made somewhat stricter when class-level variables are > considered. Previously, when the string form wasn't used in annotations, > a class decorator would be able to cover situations like:: > > @class_decorator > class Restaurant: > class MenuOption(Enum): > SPAM = 1 > EGGS = 2 > > default_menu: List[MenuOption] = [] > > This is no longer possible. > > 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/class > definition are now invalid. Example:: > > def generate_class(): > some_local = datetime.datetime.now() > class C: > field: some_local = 1 # NOTE: INVALID ANNOTATION > def method(self, arg: some_local.day) -> None: # NOTE: INVALID > ANNOTATION > ... > > Annotations using nested classes and their respective state are still > valid, provided they use the fully qualified name. Example:: > > class C: > field = 'c_field' > def method(self, arg: C.field) -> None: # this is OK > ... > > class D: > field2 = 'd_field' > def method(self, arg: C.field -> C.D.field2: # this is OK > ... > > In the presence of an annotation that cannot be resolved using the > current module's globals, a NameError is raised at compile time. > > > Deprecation policy > ------------------ > > In Python 3.7, a ``__future__`` import is required to use the described > functionality and a ``PendingDeprecationWarning`` is raised by the > compiler in the presence of type annotations in modules without the > ``__future__`` import. In Python 3.8 the warning becomes a > ``DeprecationWarning``. In the next version this will become the > default behavior. > > > Rejected Ideas > ============== > > Keep the ability to use local state when defining annotations > ------------------------------------------------------------- > > With postponed evaluation, this is impossible for function locals. For > classes, it would be possible to keep the ability to define annotations > using the local scope. However, when using ``eval()`` to perform the > postponed evaluation, we need to provide the correct globals and locals > to the ``eval()`` call. In the face of nested classes, the routine to > get the effective "globals" at definition time would have to look > something like this:: > > def get_class_globals(cls): > result = {} > result.update(sys.modules[cls.__module__].__dict__) > for child in cls.__qualname__.split('.'): > result.update(result[child].__dict__) > return result > > This is brittle and doesn't even cover slots. Requiring the use of > module-level names simplifies runtime evaluation and provides the > "one obvious way" to read annotations. It's the equivalent of absolute > imports. > > > Acknowledgements > ================ > > This document could not be completed without valuable input, > encouragement and advice from Guido van Rossum, Jukka Lehtosalo, and > Ivan Levkivskyi. > > > 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: > > > _______________________________________________ > Python-ideas mailing list > Python-ideas@python.org > https://mail.python.org/mailman/listinfo/python-ideas > Code of Conduct: http://python.org/psf/codeofconduct/ > -- Ryan (ライアン) Yoko Shimomura, ryo (supercell/EGOIST), Hiroyuki Sawano >> everyone else http://refi64.com/ _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/