Pep567 v2 (#524)

* pep-567: Update C API; get_context() -> copy_context()

* pep-567: Add ref implementation; update C API; copy_context()
This commit is contained in:
Yury Selivanov 2017-12-28 01:01:05 -05:00 committed by GitHub
parent f7316723e9
commit 25324f863f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 39 additions and 21 deletions

View File

@ -8,7 +8,7 @@ Type: Standards Track
Content-Type: text/x-rst Content-Type: text/x-rst
Created: 12-Dec-2017 Created: 12-Dec-2017
Python-Version: 3.7 Python-Version: 3.7
Post-History: 12-Dec-2017 Post-History: 12-Dec-2017, 28-Dec-2017
Abstract Abstract
@ -25,6 +25,10 @@ difference is that this PEP is concerned only with solving the case
for asynchronous tasks, not for generators. There are no proposed for asynchronous tasks, not for generators. There are no proposed
modifications to any built-in types or to the interpreter. modifications to any built-in types or to the interpreter.
This proposal is not strictly related to Python Context Managers.
Although it does provide a mechanism that can be used by Context
Managers to store their state.
Rationale Rationale
========= =========
@ -73,7 +77,7 @@ may have different values for the same key. This idea is well-known
from thread-local storage but in this case the locality of the value is from thread-local storage but in this case the locality of the value is
not necessarily bound to a thread. Instead, there is the notion of the not necessarily bound to a thread. Instead, there is the notion of the
"current ``Context``" which is stored in thread-local storage, and "current ``Context``" which is stored in thread-local storage, and
is accessed via ``contextvars.get_context()`` function. is accessed via ``contextvars.copy_context()`` function.
Manipulation of the current ``Context`` is the responsibility of the Manipulation of the current ``Context`` is the responsibility of the
task framework, e.g. asyncio. task framework, e.g. asyncio.
@ -94,8 +98,8 @@ Specification
A new standard library module ``contextvars`` is added with the A new standard library module ``contextvars`` is added with the
following APIs: following APIs:
1. ``get_context() -> Context`` function is used to get the current 1. ``copy_context() -> Context`` function is used to get a copy of
``Context`` object for the current OS thread. the current ``Context`` object for the current OS thread.
2. ``ContextVar`` class to declare and access context variables. 2. ``ContextVar`` class to declare and access context variables.
@ -180,11 +184,11 @@ contextvars.Context
``Context`` object is a mapping of context variables to values. ``Context`` object is a mapping of context variables to values.
``Context()`` creates an empty context. To get the current ``Context`` ``Context()`` creates an empty context. To get a copy of the current
for the current OS thread, use the ``contextvars.get_context()`` ``Context`` for the current OS thread, use the
method:: ``contextvars.copy_context()`` method::
ctx = contextvars.get_context() ctx = contextvars.copy_context()
To run Python code in some ``Context``, use ``Context.run()`` To run Python code in some ``Context``, use ``Context.run()``
method:: method::
@ -203,7 +207,7 @@ be contained in the ``ctx`` context::
var.set('ham') var.set('ham')
assert var.get() == 'ham' assert var.get() == 'ham'
ctx = get_context() ctx = copy_context()
# Any changes that 'function' makes to 'var' will stay # Any changes that 'function' makes to 'var' will stay
# isolated in the 'ctx'. # isolated in the 'ctx'.
@ -219,7 +223,7 @@ callbacks and Tasks are executed. It can also be used to run some
code in a different thread in the context of the current thread:: code in a different thread in the context of the current thread::
executor = ThreadPoolExecutor() executor = ThreadPoolExecutor()
current_context = contextvars.get_context() current_context = contextvars.copy_context()
executor.submit( executor.submit(
lambda: current_context.run(some_function)) lambda: current_context.run(some_function))
@ -227,7 +231,7 @@ code in a different thread in the context of the current thread::
``Context`` objects implement the ``collections.abc.Mapping`` ABC. ``Context`` objects implement the ``collections.abc.Mapping`` ABC.
This can be used to introspect context objects:: This can be used to introspect context objects::
ctx = contextvars.get_context() ctx = contextvars.copy_context()
# Print all context variables and their values in 'ctx': # Print all context variables and their values in 'ctx':
print(ctx.items()) print(ctx.items())
@ -250,7 +254,7 @@ keyword-only argument, which defaults to the current context::
def call_soon(self, callback, *args, context=None): def call_soon(self, callback, *args, context=None):
if context is None: if context is None:
context = contextvars.get_context() context = contextvars.copy_context()
# ... some time later # ... some time later
context.run(callback, *args) context.run(callback, *args)
@ -263,7 +267,7 @@ as follows::
def __init__(self, coro): def __init__(self, coro):
... ...
# Get the current context snapshot. # Get the current context snapshot.
self._context = contextvars.get_context() self._context = contextvars.copy_context()
self._loop.call_soon(self._step, context=self._context) self._loop.call_soon(self._step, context=self._context)
def _step(self, exc=None): def _step(self, exc=None):
@ -280,8 +284,14 @@ C API
1. ``PyContextVar * PyContextVar_New(char *name, PyObject *default)``: 1. ``PyContextVar * PyContextVar_New(char *name, PyObject *default)``:
create a ``ContextVar`` object. create a ``ContextVar`` object.
2. ``PyObject * PyContextVar_Get(PyContextVar *)``: 2. ``int PyContextVar_Get(PyContextVar *, PyObject *default_value, PyObject **value)``:
return the value of the variable in the current context. return ``-1`` if an error occurs during the lookup, ``0`` otherwise.
If a value for the context variable is found, it will be set to the
``value`` pointer. Otherwise, ``value`` will be set to
``default_value`` when it is not ``NULL``. If ``default_value`` is
``NULL``, ``value`` will be set to the default value of the
variable, which can be ``NULL`` too. ``value`` is always a borrowed
reference.
3. ``PyContextToken * PyContextVar_Set(PyContextVar *, PyObject *)``: 3. ``PyContextToken * PyContextVar_Set(PyContextVar *, PyObject *)``:
set the value of the variable in the current context. set the value of the variable in the current context.
@ -291,14 +301,14 @@ C API
5. ``PyContext * PyContext_New()``: create a new empty context. 5. ``PyContext * PyContext_New()``: create a new empty context.
6. ``PyContext * PyContext_Get()``: get the current context. 6. ``PyContext * PyContext_Copy()``: get a copy of the current context.
7. ``int PyContext_Enter(PyContext *)`` and 7. ``int PyContext_Enter(PyContext *)`` and
``int PyContext_Exit(PyContext *)`` allow to set and restore ``int PyContext_Exit(PyContext *)`` allow to set and restore
the context for the current OS thread. It is required to always the context for the current OS thread. It is required to always
restore the previous context:: restore the previous context::
PyContext *old_ctx = PyContext_Get(); PyContext *old_ctx = PyContext_Copy();
if (old_ctx == NULL) goto error; if (old_ctx == NULL) goto error;
if (PyContext_Enter(new_ctx)) goto error; if (PyContext_Enter(new_ctx)) goto error;
@ -345,9 +355,9 @@ points to a ``_ContextData`` object::
class PyThreadState: class PyThreadState:
context_data: _ContextData context_data: _ContextData
``contextvars.get_context()`` is implemented as follows:: ``contextvars.copy_context()`` is implemented as follows::
def get_context(): def copy_context():
ts : PyThreadState = PyThreadState_Get() ts : PyThreadState = PyThreadState_Get()
if ts.context_data is None: if ts.context_data is None:
@ -456,7 +466,7 @@ Implementation Notes
* The internal immutable dictionary for ``Context`` is implemented * The internal immutable dictionary for ``Context`` is implemented
using Hash Array Mapped Tries (HAMT). They allow for O(log N) using Hash Array Mapped Tries (HAMT). They allow for O(log N)
``set`` operation, and for O(1) ``get_context()`` function, where ``set`` operation, and for O(1) ``copy_context()`` function, where
*N* is the number of items in the dictionary. For a detailed *N* is the number of items in the dictionary. For a detailed
analysis of HAMT performance please refer to :pep:`550` [1]_. analysis of HAMT performance please refer to :pep:`550` [1]_.
@ -472,7 +482,7 @@ Summary of the New APIs
======================= =======================
* A new ``contextvars`` module with ``ContextVar``, ``Context``, * A new ``contextvars`` module with ``ContextVar``, ``Context``,
and ``Token`` classes, and a ``get_context()`` function. and ``Token`` classes, and a ``copy_context()`` function.
* ``asyncio.Loop.call_at()``, ``asyncio.Loop.call_later()``, * ``asyncio.Loop.call_at()``, ``asyncio.Loop.call_later()``,
``asyncio.Loop.call_soon()``, and ``asyncio.Loop.call_soon()``, and
@ -541,6 +551,12 @@ code unmodified, but will automatically enable support for
asynchronous code. asynchronous code.
Reference Implementation
========================
The reference implementation can be found here: [3]_.
References References
========== ==========
@ -548,6 +564,8 @@ References
.. [2] https://www.python.org/dev/peps/pep-0550/#replication-of-threading-local-interface .. [2] https://www.python.org/dev/peps/pep-0550/#replication-of-threading-local-interface
.. [3] https://github.com/python/cpython/pull/5027
Copyright Copyright
========= =========