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:
parent
f7316723e9
commit
25324f863f
60
pep-0567.rst
60
pep-0567.rst
|
@ -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
|
||||||
=========
|
=========
|
||||||
|
|
Loading…
Reference in New Issue