[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


Hi,

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
Yury.

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():
    name.set('yury')
    print(f"context[name]: {context.get(name)}")
    print(f"name in context: {name in context}")

context = contextvars.copy_context()
context.run(func)
---

Output:
---
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:

https://github.com/vstinner/cpython/commit/1e5ee71c15e2b1387c888d6eca2b08ef14595130
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
        else:
            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

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

Victor


More information about the Python-Dev mailing list