[Python-ideas] PEP 563: Postponed Evaluation of Annotations, first draft

אלעזר elazarg at gmail.com
Mon Sep 11 12:06:14 EDT 2017


I like it. For previous discussion of this idea see here:
https://mail.python.org/pipermail/python-ideas/2016-September/042527.html

I don't see this mentioned in the PEP, but it will also allow (easy)
description of contracts and dependent types.

Elazar

On Mon, Sep 11, 2017 at 6:59 PM Lukasz Langa <lukasz at langa.pl> wrote:

> PEP: 563
> Title: Postponed Evaluation of Annotations
> Version: $Revision$
> Last-Modified: $Date$
> Author: Łukasz Langa <lukasz at langa.pl>
> Discussions-To: Python-Dev <python-dev at 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 at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20170911/cb8b0a83/attachment.html>


More information about the Python-ideas mailing list