[Python-Dev] PEP 563: Postponed Evaluation of Annotations (original) (raw)

Nick Coghlan ncoghlan at gmail.com
Fri Nov 3 02:22:36 EDT 2017


On 3 November 2017 at 03:00, Brett Cannon <brett at python.org> wrote:

The cost of constructing some of the objects used as type hints can be very expensive and make importing really expensive (this has been pointed out by Lukasz previously as well as Inada-san). By making Python itself not have to construct objects from e.g. the 'typing' module at runtime, you then don't pay a runtime penalty for something you're almost never going to use at runtime anyway.

Another point worth noting is that merely importing the typing module is expensive:

$ python -m perf timeit -s "from importlib import reload; import typing" "reload(typing)" ..................... Mean +- std dev: 10.6 ms +- 0.6 ms

10 ms is a big chunk out of a CLI application's startup time budget.

So I think to be truly effective in achieving its goals, the PEP will also need to promote TYPE_CHECKING to a builtin, such that folks can write:

from __future__ import lazy_annotations # More self-explanatory name
if TYPE_CHECKING:
    import typing

and be able to express their type annotations in a way that a static type checker will understand, while incurring near-zero runtime overhead.

[snip]

Regarding the thunk idea: the compiler can pretty easily rewrite all annotations from being "" to "lambda: ".

This has a couple of very nice properties:

The big downside to this approach is that it makes simple annotations more expensive rather than less expensive.

Baseline (mentioning a builtin): $ python -m perf timeit "str" ..................... Mean +- std dev: 27.1 ns +- 1.4 ns

String constant (~4x speedup): $ python -m perf timeit "'str'" ..................... Mean +- std dev: 7.84 ns +- 0.22 ns

Lambda expression (~2x slowdown): $ python -m perf timeit "lambda: str" ..................... Mean +- std dev: 62.3 ns +- 1.4 ns

That said, I'll also point out the following:

The other key downside to the lambda based approach is that it hits the same backwards compatibility problem we hit when list comprehensions were given their own nested scope: if we push annotations down into a new scope, they won't be able to see class level attributes any more. For comprehensions, we could partially mitigate that by evaluating the outermost iterable expression in the class scope, but there's no equivalent to that available for annotations (since the annotation's lambda expression may never be called at all).

Cheers, Nick.

P.S. Relative performance of the annotation styles in a nested function definition

Baseline: $ python -m perf timeit -s "def f(): str" "f()" ..................... Mean +- std dev: 103 ns +- 3 ns

String constant (~1.25x speedup): $ python -m perf timeit -s "def f(): 'str'" "f()" ..................... Mean +- std dev: 77.0 ns +- 1.7 ns

Lambda expression (~1.5x slowdown): $ python -m perf timeit -s "def f(): (lambda: str)" "f()" ..................... Mean +- std dev: 149 ns +- 6 ns

-- Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia



More information about the Python-Dev mailing list