From 722ab66fac30a4e4d0507b7e280bde7acc6a47b0 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Thu, 18 Jan 2018 00:07:14 -0500 Subject: [PATCH] pep-567: Add two more subsections to Rejected Ideas (#550) --- pep-0567.rst | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/pep-0567.rst b/pep-0567.rst index 31f0f80e1..babb76ea1 100644 --- a/pep-0567.rst +++ b/pep-0567.rst @@ -667,6 +667,104 @@ Given the time frame of the Python 3.7 release schedule it was decided to defer this proposal to Python 3.8. +Make Context a MutableMapping +----------------------------- + +Making the ``Context`` class implement the ``abc.MutableMapping`` +interface would mean that it is possible to set and unset variables +using ``Context[var] = value`` and ``del Context[var]`` operations. + +This proposal was deferred to Python 3.8+ because of the following: + +1. If in Python 3.8 it is decided that generators should support + context variables (see :pep:`550` and :pep:`568`), then ``Context`` + would be transformed into a chain-map of context variables mappings + (as every generator would have its own mapping). That would make + mutation operations like ``Context.__delitem__`` confusing, as + they would operate only on the topmost mapping of the chain. + +2. Having a single way of mutating the context + (``ContextVar.set()`` and ``ContextVar.reset()`` methods) makes + the API more straightforward. + + For example, it would be non-obvious why the below code fragment + does not work as expected:: + + var = ContextVar('var') + + ctx = copy_context() + ctx[var] = 'value' + print(ctx[var]) # Prints 'value' + + print(var.get()) # Raises a LookupError + + While the following code would work:: + + ctx = copy_context() + + def func(): + ctx[var] = 'value' + + # Contrary to the previous example, this would work + # because 'func()' is running within 'ctx'. + print(ctx[var]) + print(var.get()) + + ctx.run(func) + + +Have initial values for ContextVars +----------------------------------- + +Nathaniel Smith proposed to have a required ``initial_value`` +keyword-only argument for the ``ContextVar`` constructor. + +The main argument against this proposal is that for some types +there is simply no sensible "initial value" except ``None``. +E.g. consider a web framework that stores the current HTTP +request object in a context variable. With the current semantics +it is possible to create a context variable without a default value:: + + # Framework: + current_request: ContextVar[Request] = \ + ContextVar('current_request') + + + # Later, while handling an HTTP request: + request: Request = current_request.get() + + # Work with the 'request' object: + return request.method + +Note that in the above example there is no need to check if +``request`` is ``None``. It is simply expected that the framework +always sets the ``current_request`` variable, or it is a bug (in +which case ``current_request.get()`` would raise a ``LookupError``). + +If, however, we had a required initial value, we would have +to guard against ``None`` values explicitly:: + + # Framework: + current_request: ContextVar[Optional[Request]] = \ + ContextVar('current_request', initial_value=None) + + + # Later, while handling an HTTP request: + request: Optional[Request] = current_request.get() + + # Check if the current request object was set: + if request is None: + raise RuntimeError + + # Work with the 'request' object: + return request.method + +Moreover, we can loosely compare context variables to regular +Python variables and to ``threading.local()`` objects. Both +of them raise errors on failed lookups (``NameError`` and +``AttributeError`` respectively). + + Backwards Compatibility =======================