<div dir="ltr"><div>I have a couple basic questions around how this API could be used in practice. Both of my questions are for the Python API as applied to Tasks in asyncio.</div><div><br></div><div>1) Would this API support looking up the value of a context variable for **another** Task? For example, if you're managing multiple tasks using asyncio.wait() and there is an exception in some task, you might want to examine and report the value of a context variable for that task.</div><div><br></div><div>2) Would an appropriate use of this API be to assign a unique task id to each task? Or can that be handled more simply? I'm wondering because I recently thought this would be useful, and it doesn't seem like asyncio means for one to subclass Task (though I could be wrong).</div><div><br></div><div>Thanks,</div><div>--Chris</div><div><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Dec 27, 2017 at 10:08 PM, Yury Selivanov <span dir="ltr"><<a href="mailto:yselivanov.ml@gmail.com" target="_blank">yselivanov.ml@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">This is a second version of PEP 567.<br>
<br>
A few things have changed:<br>
<br>
1. I now have a reference implementation:<br>
<a href="https://github.com/python/cpython/pull/5027" rel="noreferrer" target="_blank">https://github.com/python/<wbr>cpython/pull/5027</a><br>
<br>
2. The C API was updated to match the implementation.<br>
<br>
3. The get_context() function was renamed to copy_context() to better<br>
reflect what it is really doing.<br>
<br>
4. Few clarifications/edits here and there to address earlier feedback.<br>
<br>
<br>
Yury<br>
<br>
<br>
PEP: 567<br>
Title: Context Variables<br>
Version: $Revision$<br>
Last-Modified: $Date$<br>
Author: Yury Selivanov <<a href="mailto:yury@magic.io">yury@magic.io</a>><br>
Status: Draft<br>
Type: Standards Track<br>
Content-Type: text/x-rst<br>
Created: 12-Dec-2017<br>
Python-Version: 3.7<br>
Post-History: 12-Dec-2017, 28-Dec-2017<br>
<br>
<br>
Abstract<br>
========<br>
<br>
This PEP proposes a new ``contextvars`` module and a set of new<br>
CPython C APIs to support context variables.  This concept is<br>
similar to thread-local storage (TLS), but, unlike TLS, it also allows<br>
correctly keeping track of values per asynchronous task, e.g.<br>
``asyncio.Task``.<br>
<br>
This proposal is a simplified version of :pep:`550`.  The key<br>
difference is that this PEP is concerned only with solving the case<br>
for asynchronous tasks, not for generators.  There are no proposed<br>
modifications to any built-in types or to the interpreter.<br>
<br>
This proposal is not strictly related to Python Context Managers.<br>
Although it does provide a mechanism that can be used by Context<br>
Managers to store their state.<br>
<br>
<br>
Rationale<br>
=========<br>
<br>
Thread-local variables are insufficient for asynchronous tasks that<br>
execute concurrently in the same OS thread.  Any context manager that<br>
saves and restores a context value using ``threading.local()`` will<br>
have its context values bleed to other code unexpectedly when used<br>
in async/await code.<br>
<br>
A few examples where having a working context local storage for<br>
asynchronous code is desirable:<br>
<br>
* Context managers like ``decimal`` contexts and ``numpy.errstate``.<br>
<br>
* Request-related data, such as security tokens and request<br>
  data in web applications, language context for ``gettext``, etc.<br>
<br>
* Profiling, tracing, and logging in large code bases.<br>
<br>
<br>
Introduction<br>
============<br>
<br>
The PEP proposes a new mechanism for managing context variables.<br>
The key classes involved in this mechanism are ``contextvars.Context``<br>
and ``contextvars.ContextVar``.  The PEP also proposes some policies<br>
for using the mechanism around asynchronous tasks.<br>
<br>
The proposed mechanism for accessing context variables uses the<br>
``ContextVar`` class.  A module (such as ``decimal``) that wishes to<br>
store a context variable should:<br>
<br>
* declare a module-global variable holding a ``ContextVar`` to<br>
  serve as a key;<br>
<br>
* access the current value via the ``get()`` method on the<br>
  key variable;<br>
<br>
* modify the current value via the ``set()`` method on the<br>
  key variable.<br>
<br>
The notion of "current value" deserves special consideration:<br>
different asynchronous tasks that exist and execute concurrently<br>
may have different values for the same key.  This idea is well-known<br>
from thread-local storage but in this case the locality of the value is<br>
not necessarily bound to a thread.  Instead, there is the notion of the<br>
"current ``Context``" which is stored in thread-local storage, and<br>
is accessed via ``contextvars.copy_context()`` function.<br>
Manipulation of the current ``Context`` is the responsibility of the<br>
task framework, e.g. asyncio.<br>
<br>
A ``Context`` is conceptually a read-only mapping, implemented using<br>
an immutable dictionary.  The ``ContextVar.get()`` method does a<br>
lookup in the current ``Context`` with ``self`` as a key, raising a<br>
``LookupError``  or returning a default value specified in<br>
the constructor.<br>
<br>
The ``ContextVar.set(value)`` method clones the current ``Context``,<br>
assigns the ``value`` to it with ``self`` as a key, and sets the<br>
new ``Context`` as the new current ``Context``.<br>
<br>
<br>
Specification<br>
=============<br>
<br>
A new standard library module ``contextvars`` is added with the<br>
following APIs:<br>
<br>
1. ``copy_context() -> Context`` function is used to get a copy of<br>
  Â the current ``Context`` object for the current OS thread.<br>
<br>
2. ``ContextVar`` class to declare and access context variables.<br>
<br>
3. ``Context`` class encapsulates context state.  Every OS thread<br>
  Â stores a reference to its current ``Context`` instance.<br>
  Â It is not possible to control that reference manually.<br>
  Â Instead, the ``Context.run(callable, *args, **kwargs)`` method is<br>
  Â used to run Python code in another context.<br>
<br>
<br>
contextvars.ContextVar<br>
----------------------<br>
<br>
The ``ContextVar`` class has the following constructor signature:<br>
``ContextVar(name, *, default=_NO_DEFAULT)``.  The ``name`` parameter<br>
is used only for introspection and debug purposes, and is exposed<br>
as a read-only ``ContextVar.name`` attribute.  The ``default``<br>
parameter is optional.  Example::<br>
<br>
  Â  # Declare a context variable 'var' with the default value 42.<br>
  Â  var = ContextVar('var', default=42)<br>
<br>
(The ``_NO_DEFAULT`` is an internal sentinel object used to<br>
detect if the default value was provided.)<br>
<br>
``ContextVar.get()`` returns a value for context variable from the<br>
current ``Context``::<br>
<br>
  Â  # Get the value of `var`.<br>
  Â  var.get()<br>
<br>
``ContextVar.set(value) -> Token`` is used to set a new value for<br>
the context variable in the current ``Context``::<br>
<br>
  Â  # Set the variable 'var' to 1 in the current context.<br>
  Â  var.set(1)<br>
<br>
``ContextVar.reset(token)`` is used to reset the variable in the<br>
current context to the value it had before the ``set()`` operation<br>
that created the ``token``::<br>
<br>
  Â  assert var.get(None) is None<br>
<br>
  Â  token = var.set(1)<br>
  Â  try:<br>
  Â  Â  Â  ...<br>
  Â  finally:<br>
  Â  Â  Â  var.reset(token)<br>
<br>
  Â  assert var.get(None) is None<br>
<br>
``ContextVar.reset()`` method is idempotent and can be called<br>
multiple times on the same Token object: second and later calls<br>
will be no-ops.<br>
<br>
<br>
contextvars.Token<br>
-----------------<br>
<br>
``contextvars.Token`` is an opaque object that should be used to<br>
restore the ``ContextVar`` to its previous value, or remove it from<br>
the context if the variable was not set before.  It can be created<br>
only by calling ``ContextVar.set()``.<br>
<br>
For debug and introspection purposes it has:<br>
<br>
* a read-only attribute ``Token.var`` pointing to the variable<br>
  that created the token;<br>
<br>
* a read-only attribute ``Token.old_value`` set to the value the<br>
  variable had before the ``set()`` call, or to ``Token.MISSING``<br>
  if the variable wasn't set before.<br>
<br>
Having the ``ContextVar.set()`` method returning a ``Token`` object<br>
and the ``ContextVar.reset(token)`` method, allows context variables<br>
to be removed from the context if they were not in it before the<br>
``set()`` call.<br>
<br>
<br>
contextvars.Context<br>
-------------------<br>
<br>
``Context`` object is a mapping of context variables to values.<br>
<br>
``Context()`` creates an empty context.  To get a copy of the current<br>
``Context`` for the current OS thread, use the<br>
``contextvars.copy_context()`` method::<br>
<br>
  Â  ctx = contextvars.copy_context()<br>
<br>
To run Python code in some ``Context``, use ``Context.run()``<br>
method::<br>
<br>
  Â  ctx.run(function)<br>
<br>
Any changes to any context variables that ``function`` causes will<br>
be contained in the ``ctx`` context::<br>
<br>
  Â  var = ContextVar('var')<br>
  Â  var.set('spam')<br>
<br>
  Â  def function():<br>
  Â  Â  Â  assert var.get() == 'spam'<br>
<br>
  Â  Â  Â  var.set('ham')<br>
  Â  Â  Â  assert var.get() == 'ham'<br>
<br>
  Â  ctx = copy_context()<br>
<br>
  Â  # Any changes that 'function' makes to 'var' will stay<br>
  Â  # isolated in the 'ctx'.<br>
  Â  ctx.run(function)<br>
<br>
  Â  assert var.get() == 'spam'<br>
<br>
Any changes to the context will be contained in the ``Context``<br>
object on which ``run()`` is called on.<br>
<br>
``Context.run()`` is used to control in which context asyncio<br>
callbacks and Tasks are executed.  It can also be used to run some<br>
code in a different thread in the context of the current thread::<br>
<br>
  Â  executor = ThreadPoolExecutor()<br>
  Â  current_context = contextvars.copy_context()<br>
<br>
  Â  executor.submit(<br>
  Â  Â  Â  lambda: current_context.run(some_<wbr>function))<br>
<br>
``Context`` objects implement the ``collections.abc.Mapping`` ABC.<br>
This can be used to introspect context objects::<br>
<br>
  Â  ctx = contextvars.copy_context()<br>
<br>
  Â  # Print all context variables and their values in 'ctx':<br>
  Â  print(ctx.items())<br>
<br>
  Â  # Print the value of 'some_variable' in context 'ctx':<br>
  Â  print(ctx[some_variable])<br>
<br>
<br>
asyncio<br>
-------<br>
<br>
``asyncio`` uses ``Loop.call_soon()``, ``Loop.call_later()``,<br>
and ``Loop.call_at()`` to schedule the asynchronous execution of a<br>
function.  ``asyncio.Task`` uses ``call_soon()`` to run the<br>
wrapped coroutine.<br>
<br>
We modify ``Loop.call_{at,later,soon}`` and<br>
``Future.add_done_callback()`` to accept the new optional *context*<br>
keyword-only argument, which defaults to the current context::<br>
<br>
  Â  def call_soon(self, callback, *args, context=None):<br>
  Â  Â  Â  if context is None:<br>
  Â  Â  Â  Â  Â  context = contextvars.copy_context()<br>
<br>
  Â  Â  Â  # ... some time later<br>
  Â  Â  Â  context.run(callback, *args)<br>
<br>
Tasks in asyncio need to maintain their own context that they inherit<br>
from the point they were created at.  ``asyncio.Task`` is modified<br>
as follows::<br>
<br>
  Â  class Task:<br>
  Â  Â  Â  def __init__(self, coro):<br>
  Â  Â  Â  Â  Â  ...<br>
  Â  Â  Â  Â  Â  # Get the current context snapshot.<br>
  Â  Â  Â  Â  Â  self._context = contextvars.copy_context()<br>
  Â  Â  Â  Â  Â  self._loop.call_soon(self._<wbr>step, context=self._context)<br>
<br>
  Â  Â  Â  def _step(self, exc=None):<br>
  Â  Â  Â  Â  Â  ...<br>
  Â  Â  Â  Â  Â  # Every advance of the wrapped coroutine is done in<br>
  Â  Â  Â  Â  Â  # the task's context.<br>
  Â  Â  Â  Â  Â  self._loop.call_soon(self._<wbr>step, context=self._context)<br>
  Â  Â  Â  Â  Â  ...<br>
<br>
<br>
C API<br>
-----<br>
<br>
1. ``PyContextVar * PyContextVar_New(char *name, PyObject *default)``:<br>
  Â create a ``ContextVar`` object.<br>
<br>
2. ``int PyContextVar_Get(PyContextVar *, PyObject *default_value,<br>
PyObject **value)``:<br>
  Â return ``-1`` if an error occurs during the lookup, ``0`` otherwise.<br>
  Â If a value for the context variable is found, it will be set to the<br>
  Â ``value`` pointer.  Otherwise, ``value`` will be set to<br>
  Â ``default_value`` when it is not ``NULL``.  If ``default_value`` is<br>
  Â ``NULL``, ``value`` will be set to the default value of the<br>
  Â variable, which can be ``NULL`` too.  ``value`` is always a borrowed<br>
  Â reference.<br>
<br>
3. ``PyContextToken * PyContextVar_Set(PyContextVar *, PyObject *)``:<br>
  Â set the value of the variable in the current context.<br>
<br>
4. ``PyContextVar_Reset(<wbr>PyContextVar *, PyContextToken *)``:<br>
  Â reset the value of the context variable.<br>
<br>
5. ``PyContext * PyContext_New()``: create a new empty context.<br>
<br>
6. ``PyContext * PyContext_Copy()``: get a copy of the current context.<br>
<br>
7. ``int PyContext_Enter(PyContext *)`` and<br>
  Â ``int PyContext_Exit(PyContext *)`` allow to set and restore<br>
  Â the context for the current OS thread.  It is required to always<br>
  Â restore the previous context::<br>
<br>
  Â  Â  PyContext *old_ctx = PyContext_Copy();<br>
  Â  Â  if (old_ctx == NULL) goto error;<br>
<br>
  Â  Â  if (PyContext_Enter(new_ctx)) goto error;<br>
<br>
  Â  Â  // run some code<br>
<br>
  Â  Â  if (PyContext_Exit(old_ctx)) goto error;<br>
<br>
<br>
Implementation<br>
==============<br>
<br>
This section explains high-level implementation details in<br>
pseudo-code.  Some optimizations are omitted to keep this section<br>
short and clear.<br>
<br>
For the purposes of this section, we implement an immutable dictionary<br>
using ``dict.copy()``::<br>
<br>
  Â  class _ContextData:<br>
<br>
  Â  Â  Â  def __init__(self):<br>
  Â  Â  Â  Â  Â  self._mapping = dict()<br>
<br>
  Â  Â  Â  def get(self, key):<br>
  Â  Â  Â  Â  Â  return self._mapping[key]<br>
<br>
  Â  Â  Â  def set(self, key, value):<br>
  Â  Â  Â  Â  Â  copy = _ContextData()<br>
  Â  Â  Â  Â  Â  copy._mapping = self._mapping.copy()<br>
  Â  Â  Â  Â  Â  copy._mapping[key] = value<br>
  Â  Â  Â  Â  Â  return copy<br>
<br>
  Â  Â  Â  def delete(self, key):<br>
  Â  Â  Â  Â  Â  copy = _ContextData()<br>
  Â  Â  Â  Â  Â  copy._mapping = self._mapping.copy()<br>
  Â  Â  Â  Â  Â  del copy._mapping[key]<br>
  Â  Â  Â  Â  Â  return copy<br>
<br>
Every OS thread has a reference to the current ``_ContextData``.<br>
``PyThreadState`` is updated with a new ``context_data`` field that<br>
points to a ``_ContextData`` object::<br>
<br>
  Â  class PyThreadState:<br>
  Â  Â  Â  context_data: _ContextData<br>
<br>
``contextvars.copy_context()`` is implemented as follows::<br>
<br>
  Â  def copy_context():<br>
  Â  Â  Â  ts : PyThreadState = PyThreadState_Get()<br>
<br>
  Â  Â  Â  if ts.context_data is None:<br>
  Â  Â  Â  Â  Â  ts.context_data = _ContextData()<br>
<br>
  Â  Â  Â  ctx = Context()<br>
  Â  Â  Â  ctx._data = ts.context_data<br>
  Â  Â  Â  return ctx<br>
<br>
``contextvars.Context`` is a wrapper around ``_ContextData``::<br>
<br>
  Â  class Context(collections.abc.<wbr>Mapping):<br>
<br>
  Â  Â  Â  def __init__(self):<br>
  Â  Â  Â  Â  Â  self._data = _ContextData()<br>
<br>
  Â  Â  Â  def run(self, callable, *args, **kwargs):<br>
  Â  Â  Â  Â  Â  ts : PyThreadState = PyThreadState_Get()<br>
  Â  Â  Â  Â  Â  saved_data : _ContextData = ts.context_data<br>
<br>
  Â  Â  Â  Â  Â  try:<br>
  Â  Â  Â  Â  Â  Â  Â  ts.context_data = self._data<br>
  Â  Â  Â  Â  Â  Â  Â  return callable(*args, **kwargs)<br>
  Â  Â  Â  Â  Â  finally:<br>
  Â  Â  Â  Â  Â  Â  Â  self._data = ts.context_data<br>
  Â  Â  Â  Â  Â  Â  Â  ts.context_data = saved_data<br>
<br>
  Â  Â  Â  # Mapping API methods are implemented by delegating<br>
  Â  Â  Â  # `get()` and other Mapping calls to `self._data`.<br>
<br>
``contextvars.ContextVar`` interacts with<br>
``PyThreadState.context_data`` directly::<br>
<br>
  Â  class ContextVar:<br>
<br>
  Â  Â  Â  def __init__(self, name, *, default=_NO_DEFAULT):<br>
  Â  Â  Â  Â  Â  self._name = name<br>
  Â  Â  Â  Â  Â  self._default = default<br>
<br>
  Â  Â  Â  @property<br>
  Â  Â  Â  def name(self):<br>
  Â  Â  Â  Â  Â  return self._name<br>
<br>
  Â  Â  Â  def get(self, default=_NO_DEFAULT):<br>
  Â  Â  Â  Â  Â  ts : PyThreadState = PyThreadState_Get()<br>
  Â  Â  Â  Â  Â  data : _ContextData = ts.context_data<br>
<br>
  Â  Â  Â  Â  Â  try:<br>
  Â  Â  Â  Â  Â  Â  Â  return data.get(self)<br>
  Â  Â  Â  Â  Â  except KeyError:<br>
  Â  Â  Â  Â  Â  Â  Â  pass<br>
<br>
  Â  Â  Â  Â  Â  if default is not _NO_DEFAULT:<br>
  Â  Â  Â  Â  Â  Â  Â  return default<br>
<br>
  Â  Â  Â  Â  Â  if self._default is not _NO_DEFAULT:<br>
  Â  Â  Â  Â  Â  Â  Â  return self._default<br>
<br>
  Â  Â  Â  Â  Â  raise LookupError<br>
<br>
  Â  Â  Â  def set(self, value):<br>
  Â  Â  Â  Â  Â  ts : PyThreadState = PyThreadState_Get()<br>
  Â  Â  Â  Â  Â  data : _ContextData = ts.context_data<br>
<br>
  Â  Â  Â  Â  Â  try:<br>
  Â  Â  Â  Â  Â  Â  Â  old_value = data.get(self)<br>
  Â  Â  Â  Â  Â  except KeyError:<br>
  Â  Â  Â  Â  Â  Â  Â  old_value = Token.MISSING<br>
<br>
  Â  Â  Â  Â  Â  ts.context_data = data.set(self, value)<br>
  Â  Â  Â  Â  Â  return Token(self, old_value)<br>
<br>
  Â  Â  Â  def reset(self, token):<br>
  Â  Â  Â  Â  Â  if token._used:<br>
  Â  Â  Â  Â  Â  Â  Â  return<br>
<br>
  Â  Â  Â  Â  Â  if token._old_value is Token.MISSING:<br>
  Â  Â  Â  Â  Â  Â  Â  ts.context_data = data.delete(token._var)<br>
  Â  Â  Â  Â  Â  else:<br>
  Â  Â  Â  Â  Â  Â  Â  ts.context_data = data.set(token._var,<br>
  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â token._old_value)<br>
<br>
  Â  Â  Â  Â  Â  token._used = True<br>
<br>
<br>
  Â  class Token:<br>
<br>
  Â  Â  Â  MISSING = object()<br>
<br>
  Â  Â  Â  def __init__(self, var, old_value):<br>
  Â  Â  Â  Â  Â  self._var = var<br>
  Â  Â  Â  Â  Â  self._old_value = old_value<br>
  Â  Â  Â  Â  Â  self._used = False<br>
<br>
  Â  Â  Â  @property<br>
  Â  Â  Â  def var(self):<br>
  Â  Â  Â  Â  Â  return self._var<br>
<br>
  Â  Â  Â  @property<br>
  Â  Â  Â  def old_value(self):<br>
  Â  Â  Â  Â  Â  return self._old_value<br>
<br>
<br>
Implementation Notes<br>
====================<br>
<br>
* The internal immutable dictionary for ``Context`` is implemented<br>
  using Hash Array Mapped Tries (HAMT).  They allow for O(log N)<br>
  ``set`` operation, and for O(1) ``copy_context()`` function, where<br>
  *N* is the number of items in the dictionary.  For a detailed<br>
  analysis of HAMT performance please refer to :pep:`550` [1]_.<br>
<br>
* ``ContextVar.get()`` has an internal cache for the most recent<br>
  value, which allows to bypass a hash lookup.  This is similar<br>
  to the optimization the ``decimal`` module implements to<br>
  retrieve its context from ``PyThreadState_GetDict()``.<br>
  See :pep:`550` which explains the implementation of the cache<br>
  in a great detail.<br>
<br>
<br>
Summary of the New APIs<br>
=======================<br>
<br>
* A new ``contextvars`` module with ``ContextVar``, ``Context``,<br>
  and ``Token`` classes, and a ``copy_context()`` function.<br>
<br>
* ``asyncio.Loop.call_at()``, ``asyncio.Loop.call_later()``,<br>
  ``asyncio.Loop.call_soon()``, and<br>
  ``asyncio.Future.add_done_<wbr>callback()`` run callback functions in<br>
  the context they were called in.  A new *context* keyword-only<br>
  parameter can be used to specify a custom context.<br>
<br>
* ``asyncio.Task`` is modified internally to maintain its own<br>
  context.<br>
<br>
<br>
Design Considerations<br>
=====================<br>
<br>
Why contextvars.Token and not ContextVar.unset()?<br>
------------------------------<wbr>-------------------<br>
<br>
The Token API allows to get around having a ``ContextVar.unset()``<br>
method, which is incompatible with chained contexts design of<br>
:pep:`550`.  Future compatibility with :pep:`550` is desired<br>
(at least for Python 3.7) in case there is demand to support<br>
context variables in generators and asynchronous generators.<br>
<br>
The Token API also offers better usability: the user does not have<br>
to special-case absence of a value. Compare::<br>
<br>
  Â  token = cv.get()<br>
  Â  try:<br>
  Â  Â  Â  cv.set(blah)<br>
  Â  Â  Â  # code<br>
  Â  finally:<br>
  Â  Â  Â  cv.reset(token)<br>
<br>
with::<br>
<br>
  Â  _deleted = object()<br>
  Â  old = cv.get(default=_deleted)<br>
  Â  try:<br>
  Â  Â  Â  cv.set(blah)<br>
  Â  Â  Â  # code<br>
  Â  finally:<br>
  Â  Â  Â  if old is _deleted:<br>
  Â  Â  Â  Â  Â  cv.unset()<br>
  Â  Â  Â  else:<br>
  Â  Â  Â  Â  Â  cv.set(old)<br>
<br>
<br>
Rejected Ideas<br>
==============<br>
<br>
Replication of threading.local() interface<br>
------------------------------<wbr>------------<br>
<br>
Please refer to :pep:`550` where this topic is covered in detail: [2]_.<br>
<br>
<br>
Backwards Compatibility<br>
=======================<br>
<br>
This proposal preserves 100% backwards compatibility.<br>
<br>
Libraries that use ``threading.local()`` to store context-related<br>
values, currently work correctly only for synchronous code.  Switching<br>
them to use the proposed API will keep their behavior for synchronous<br>
code unmodified, but will automatically enable support for<br>
asynchronous code.<br>
<br>
<br>
Reference Implementation<br>
========================<br>
<br>
The reference implementation can be found here: [3]_.<br>
<br>
<br>
References<br>
==========<br>
<br>
.. [1] <a href="https://www.python.org/dev/peps/pep-0550/#appendix-hamt-performance-analysis" rel="noreferrer" target="_blank">https://www.python.org/dev/<wbr>peps/pep-0550/#appendix-hamt-<wbr>performance-analysis</a><br>
<br>
.. [2] <a href="https://www.python.org/dev/peps/pep-0550/#replication-of-threading-local-interface" rel="noreferrer" target="_blank">https://www.python.org/dev/<wbr>peps/pep-0550/#replication-of-<wbr>threading-local-interface</a><br>
<br>
.. [3] <a href="https://github.com/python/cpython/pull/5027" rel="noreferrer" target="_blank">https://github.com/python/<wbr>cpython/pull/5027</a><br>
<br>
<br>
Copyright<br>
=========<br>
<br>
This document has been placed in the public domain.<br>
<br>
<br>
..<br>
  Â Local Variables:<br>
  Â mode: indented-text<br>
  Â indent-tabs-mode: nil<br>
  Â sentence-end-double-space: t<br>
  Â fill-column: 70<br>
  Â coding: utf-8<br>
  Â End:<br>
______________________________<wbr>_________________<br>
Python-Dev mailing list<br>
<a href="mailto:Python-Dev@python.org">Python-Dev@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-dev" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>mailman/listinfo/python-dev</a><br>
Unsubscribe: <a href="https://mail.python.org/mailman/options/python-dev/chris.jerdonek%40gmail.com" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>mailman/options/python-dev/<wbr>chris.jerdonek%40gmail.com</a><br>
</blockquote></div><br></div>