[Python-Dev] PEP 567 (contextvars) idea: really implement the current context

Victor Stinner victor.stinner at gmail.com
Wed Jan 3 20:42:50 EST 2018


It seems like many people, including myself, are confused by the lack
of concrete current context in the PEP 567 (contextvars). But it isn't
difficult to implement the current context (I implemented it, see
below). It might have a *minor* impact on performance, but Context
mapping API is supposed to only be used for introspection according to

With contextvars.get_context(), it becomes possible to introspect the
current context, rather than a copy, and it becomes even more obvious
than a context is mutable ;-)

vstinner at apu$ ./python
>>> import contextvars
>>> ctx=contextvars.get_context()
>>> name=contextvars.ContextVar('name', default='victor')
>>> print(list(ctx.items()))

>>> name.set('yury')
<Token object at 0x7f4cc71f8ad8>
>>> print(list(ctx.items()))
[(<ContextVar name='name' default='victor' at 0x7f4cc71f18d8>, 'yury')]

With my changes, the running context remains up to date in
Context.run(). Example:
import contextvars

name = contextvars.ContextVar('name', default='victor')

def func():
    print(f"context[name]: {context.get(name)}")
    print(f"name in context: {name in context}")

context = contextvars.copy_context()

context[name]: yury
name in context: True

Compare it to the output without my changes:
context[name]: None
name in context: False

If we have contextvars.get_context(), maybe contextvars.copy_context()
can be removed and add a new Context.copy() method instead:

   new_context = contextvars.get_context().copy()


I implemented contextvars.get_context() which returns the current context:

from https://github.com/vstinner/cpython/commits/current_context

I added a context thread local storage (TLS):

class PyThreadState:
    context: Context  # can be None
    context_data: _ContextData

I modified Context mapping API to use the context variables from the
current thread state if it's the current thread state.

PyContext_Enter() now not only sets PyThreadState.context_data, but
also PyThreadState.context to itself.

Pseudo-code for Context.get():
class Context(collections.abc.Mapping):

    def __init__(self):
        self._data = _ContextData()

    def _get_data(self):
        ts : PyThreadState = PyThreadState_Get()
        if ts.context is self:
            return ts.context_data
            return self._data

    def get(self, var):  # FIXME: implement default
        data = self._get_data()
        return data.get(var)

And Context.run() is modified to set also the context TLS:
    def run(self, callable, *args, **kwargs):
        ts : PyThreadState = PyThreadState_Get()
        saved_context : Optional[Context] = ts.context  # NEW
        saved_data : _ContextData = ts.context_data

            ts.context_data = self._data
            return callable(*args, **kwargs)
            self._data = ts.context_data
            ts.context = saved_context  # NEW
            ts.context_data = saved_data


More information about the Python-Dev mailing list