[Python-Dev] PEP 550 v3

Nick Coghlan ncoghlan at gmail.com
Mon Aug 21 10:12:04 EDT 2017


On 21 August 2017 at 15:03, Guido van Rossum <guido at python.org> wrote:
> Honestly I'm not sure we need the distinction between LC and EC. If you read
> carefully some of the given example code seems to confuse them. If we could
> get away with only a single framework-facing concept, I would be happy
> calling it ExecutionContext.

Unfortunately, I don't think we can, and that's why I tried to reframe
the discussion in terms of "Where ContextKey.set() writes to" and
"Where ContextKey.get() looks things up".

Consider the following toy generator:

    def tracking_gen():
        start_tracking_iterations()
        while True:
            tally_iteration()
            yield

    task_id = ContextKey("task_id")
    iter_counter = ContextKey("iter_counter")

    def start_tracking_iterations():
        iter_counter.set(collection.Counter())

    def tally_iteration():
        current_task = task_id.get() # Set elsewhere
        iter_counter.get()[current_task] += 1

Now, this isn't a very *sensible* generator (since it could just use a
regular object instance for tracking instead of a context variable),
but nevertheless, it's one that we would expect to work, and it's one
that we would expect to exhibit the following properties:

1. When tally_iteration() calls task_id.get(), we expect that to be
resolved in the context calling next() on the instance, *not* the
context where the generator was first created
2. When tally_iteration() calls iter_counter.get(), we expect that to
be resolved in the same context where start_tracking_iterations()
called iter_counter.set()

This has consequences for the design in the PEP:

* what we want to capture at generator creation time is the context
where writes will happen, and we also want that to be the innermost
context used for lookups
* other than that innermost context, we want everything else to be dynamic
* this means that "mutable context saved on the generator" and "entire
dynamic context visible when the generator runs" aren't the same thing

And hence the introduction of the LocalContext/LogicalContext
terminology for the former, and the ExecutionContext terminology for
the latter.

It's also where the analogy with ChainMap came from (although I don't
think this has made it into the PEP itself):

* LogicalContext is the equivalent of the individual mappings
* ExecutionContext is the equivalent of ChainMap
* ContextKey.get() replaces ChainMap.__getitem__
* ContextKey.set(value) replaces ChainMap.__setitem__
* ContextKey.set(None) replaces ChainMap.__delitem__

While the context is defined conceptually as a nested chain of
key:value mappings, we avoid using the mapping syntax because of the
way the values can shift dynamically out from under you based on who
called you - while the ChainMap analogy is hopefully helpful to
understanding, we don't want people taking it too literally or things
will become more confusing rather than less.

Despite that risk, taking the analogy further is where the
DynamicWriteContext + DynamicLookupContext terminology idea came from:

* like ChainMap.new_child(), adjusting the DynamicWriteContext changes
what ck.set() affects, and also sets the innermost context for
ck.get()
* like using a different ChainMap, adjusting the DynamicLookupContext
changes what ck.get() can see (unlike ChainMap, it also isolates
ck.set() by default)

I'll also note that the first iteration of the PEP didn't really make
this distinction, and it caused a problem that Nathaniel pointed out:
generators would "snapshot" their entire dynamic context when first
created, and then never adjust it for external changes between
iterations. This meant that if you adjusted something like the decimal
context outside the generator after creating it, it would ignore those
changes - instead of having the problem of changes inside the
generator leaking out, we instead had the problem of changes outside
the generator *not* making their way in, even if you wanted them to.

Due to that heritage, fixing some of the examples could easily have
been missed in the v2 rewrite that introduced the distinction between
the two kinds of context.

> (Another critique of the proposal I have is that it adds too many
> similarly-named functions to sys. But this email is already too long and I
> need to go to bed.)

If it helps any, one of the ideas that has come up is to put all of
the proposed context manipulation APIs in contextlib rather than in
sys, and I think that's a reasonable idea (I don't think any of us
actually like the notion of adding that many new subsystem specific
APIs directly to sys).

Cheers,
Nick.

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


More information about the Python-Dev mailing list