[Python-Dev] PEP 550 V5

Yury Selivanov yselivanov.ml at gmail.com
Sun Sep 3 07:09:19 EDT 2017

Hi Eric,

On Fri, Sep 1, 2017 at 11:29 PM, Eric Snow <ericsnowcurrently at gmail.com> wrote:
> Nice working staying on top of this!  Keeping up with discussion is
> arguably much harder than actually writing the PEP. :)  I have some
> comments in-line below.


> -eric
> On Fri, Sep 1, 2017 at 5:02 PM, Yury Selivanov <yselivanov.ml at gmail.com> wrote:
>> [snip]
>> Abstract
>> ========
>> [snip]
>> Rationale
>> =========
>> [snip]
>> Goals
>> =====
> I still think that the Abstract, Rationale, and Goals sections should
> be clear that a major component of this proposal is lookup via chained
> contexts.  Without such clarity it may not be apparent that chained
> lookup is not strictly necessary to achieve the stated goals (i.e. an
> async-compatible TLS replacement).  This matters because the chaining
> introduces extra non-trivial complexity.

Let me walk you through the history of PEP 550 to explain how we
arrived to having "chained lookups".

If we didn't have generators in Python, the first version of PEP 550
([1]) would have been a perfect solution.  First let's recap the how
all versions of PEP 550 reason about generators:

* generators can be paused/resumed at any time by code they cannot control;

* thus any context changes in a generator should be isolated, and
visible only to the generator and code it runs.

PEP 550 v1 had excellent performance characteristics, a specification
that was easy to fully understand, and a relatively straightforward
implementation. It also had *no* chained lookups, as there was only
one thread-specific collection of context values.

PEP 550 states that v1 was rejected because of the following reason:
"The fundamental limitation that caused a complete redesign of the
first version was that it was not possible to implement an iterator
that would interact with the EC in the same way as generators".  I
believe it's no longer true: I think that we could use the same trick
[2] we use in the current version of PEP 550. But that doesn't really

The fundamental problem of PEP 550 v1 was that generators could not
see EC updates *while being iterated*.  Let's look at the following:

     def gen():
          yield some_decimal_calculation()
          yield some_decimal_calculation()
          yield some_decimal_calculation()

     with decimal_context_0:
         g = gen()
     with decimal_context_1:
     with decimal_context_2:

In the above example, with PEP 550 v1, generator 'g' would snapshot
the execution context at the point where it was created.  So all calls
to "some_decimal_calculation()" would be made with
"decimal_context_0".  We could easily change the semantics to snapshot
the EC at the point of the first iteration, but that would only make
"some_decimal_calculation()" to be always called with "decimal_context_1".

In this email [3], Nathaniel explained that there are use cases where
seeing updates in the surrounding Execution Context matters in some
situations. One case is his idea to implement special timeouts
handling in his async framework Trio.  Another one, is backwards
compatibility: currently, if you use a "threading.local()", you should
be able to see updates in it while the generator is being iterated.
With PEP 550 v1, you couldn't.

To fix this problem, in later versions of PEP 550, we transformed the
EC into a stack of Logical Contexts.  Every generator has its own LC,
which contains changes to the EC local to that generator.  Having a
stack naturally made it a requirement to have "chained" lookups.
That's how we fixed the above example!

Therefore, while the current version of PEP 550 is more complex than
v1, it has a *proper* support of generators. It provides strong
guarantees w.r.t. context isolation, and at the same time maintains
their ability to see the full outer context while being iterated.

> Speaking of which, I have plans for the near-to-middle future that
> involve making use of the PEP 550 functionality in a way that is quite
> similar to decimal.  However, it sounds like the implementation of
> such (namespace) contexts under PEP 550 is much more complex than it
> is with threading.local (where subclassing made it easy).  It would be
> helpful to have some direction in the PEP on how to port to PEP 550
> from threading.local.  It would be even better if the PEP included the
> addition of a contextlib.Context or contextvars.Context class (or
> NamespaceContext or ContextNamespace or ...). :)  However, I recognize
> that may be out of scope for this PEP.

Currently we are focused on refining the fundamental low-level APIs
and data structures, and the scope we cover in the PEP is already
huge.  Adding high-level APIs is much easier, so yeah, I'd discuss
them if/after the PEP is accepted.


[1] https://github.com/python/peps/blob/e8a06c9a790f39451d9e99e203b13b3ad73a1d01/pep-0550.rst
[2] https://www.python.org/dev/peps/pep-0550/#generators-transformed-into-iterators
[3] https://mail.python.org/pipermail/python-ideas/2017-August/046736.html

More information about the Python-Dev mailing list