<div dir="ltr"><div><div><div><div>I think decimal is a bit of a red herring, because the entire decimal context is stored as a single thread-local variable (_local.__decimal_context__ in _pydecimal.py, where _local is a global threading.local instance; using "___DECIMAL_CTX__" as a key in the C-level dict of thread-locals in the C version).<br><br></div>Nevertheless there is the issue of whether it's better to make contextvars.Context a MutableMapping or to make it an (immutable) Mapping.<br><br></div>From the POV of explaining the implementation, a MutableMapping is simpler. E.g. ContextVar.set() just does `get_context()[self] = value`, and ContextVar.get() is `return get_context()[self]` (some details left out relating to defaults). Token and reset() are still useful because they make it simpler to write a context manager that restores the previous value, regardless of whether a value was set or not. Context.run() makes a copy of the current context, sets that as the current context, runs the function, and then restores the previous context (the one that it copied).<br><br></div>With a truly immutable Context offering only the (immutable) Mapping interface (plus an internal API that returns a new Context that has a different value for one key), ContextVar.set() is a bit more complicated because it has to use set_context() (actually an internal thing that updates the current context in the thread state) and similar for ContextVar.reset(token). (An alternative design is possible where a Context is an immutable-looking wrapper around a plain dict, with private APIs that mutate that dict, but apart from having different invariants about the identities of Context objects it works out about the same from a user's POV.)<br></div><div><br></div><div>Anyway, the differences between these are user-visible so we can't make this an implementation detail: We have to choose. Should Context be a MutableMapping or not?</div><div><br></div><div>Yury strongly favors an immutable Context, and that's what his reference implementation has (<a href="https://github.com/python/cpython/pull/5027">https://github.com/python/cpython/pull/5027</a>). His reasoning is that in the future we *might* want to support automatic context management for generators by default (like described in his original PEP 550), and then it's essential to use the immutable version so that "copying" the context when a generator is created or resumed is super fast (and in particular O(1)). I am personally not sure that we'll ever need it but at the same time I'm also not confident that we won't, so I give Yury the benefit of the doubt here -- after all he has spent an enormous amount of time thinking this design through so I value his intuition.</div><div><br></div><div>In addition I agree that for most users the basic interface will be ContextVar, not Context, and the needs of framework authors are easily met by Context.run(). So I think that Yury's desire for an immutable Context will not harm anyone, and hence I support the current design of the PEP. (Though I want some of the details to be written up clearer -- everyone seems to agree with that. :-)</div><div><br></div><div>Maybe I should clarify again what run() does. Here's how I think of it in pseudo code:</div><div><br></div><div>def run(self, func, *args, **kwds):</div><div> old = _get_current_context()</div><div> new = old.copy()</div><div> _set_current_context(new)</div><div> try:</div><div> return func(*args, **kwds)</div><div> finally:</div><div> _set_current_context(old)</div><div><br></div><div>If you look carefully at the version in the PEP you'll see that it's the same thing, but the PEP inlines the implementations of _get_current_context() and _set_current_context() (which I made up -- these won't be real APIs) through manipulation of the current thread state.</div><div><br></div><div>I hope this clarifies everything.</div><div><br></div><div>--Guido<br></div><div><div><div><br></div></div></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Jan 3, 2018 at 2:26 AM, Victor Stinner <span dir="ltr"><<a href="mailto:victor.stinner@gmail.com" target="_blank">victor.stinner@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="auto"><span class=""><div class="gmail_extra" dir="auto"><div class="gmail_quote">Le 3 janv. 2018 06:05, "Yury Selivanov" <<a href="mailto:yselivanov.ml@gmail.com" target="_blank">yselivanov.ml@gmail.com</a>> a écrit :<blockquote class="m_8604034916003579423quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><div class="gmail_quote"><div dir="auto">tuples in Python are immutable, but you can have a tuple with a dict as its single element. The tuple is immutable, the dict is mutable.</div><div dir="auto"><br></div><div dir="auto">At the C level we have APIs that can mutate a tuple though.</div><div dir="auto"><br></div><div dir="auto">Now, tuple is not a direct analogy to Context, but there are some parallels. Context is a container like tuple, with some additional APIs on top.</div></div></div></blockquote></div></div><div dir="auto"><br></div></span><div dir="auto">Sorry, I don't think that it's a good analogy. Context.run() is a public method accessible in Python which allows to modify the context. A tuple doesn't have such method.</div><div dir="auto"><br></div><div dir="auto">While it's technically possible to modify a tuple or a str at C level, it's a bad practice leading to complex bugs when it's not done carefully: see <a href="https://bugs.python.org/issue30156" target="_blank">https://bugs.python.org/<wbr>issue30156</a> property_descr_get(<wbr>) optimization was fixed twice but still has a bug. I proposed a PR to remove the hack.</div><span class=""><div dir="auto"><br></div><div class="gmail_extra" dir="auto"><div class="gmail_quote"><blockquote class="m_8604034916003579423quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><div class="gmail_quote"><div class="m_8604034916003579423quoted-text"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="auto"><div dir="auto"><span style="font-family:sans-serif">Why Context could not inherit from MutableMapping? (Allow ctx.set(var, value) and ctx [var] = value.) Is it just to keep the API small: changes should only be made using var.set()?</span></div></div></blockquote><div dir="auto"><br></div></div><div dir="auto">Because that would be confusing to end users.</div><div dir="auto"><br></div><div dir="auto"> ctx = copy_context()</div><div dir="auto"> ctx[var] = something</div><div dir="auto"><br></div><div dir="auto">What did we just do? Did we modify the 'var' in the code that is currently executing? No, you still need to call Context.run to see the new value for var.</div></div></div></blockquote></div></div><div dir="auto"><br></div></span><div dir="auto">IMHO it's easy to understand that modifying a *copy* of the current context doesn't impact the current context. It's one the first thing to learn when learning Python:</div><div dir="auto"><br></div><div dir="auto">a = [1, 2]</div><div dir="auto">b = a.copy()</div><div dir="auto">b.append(3)</div><div dir="auto"><span style="font-family:sans-serif">assert a == [1, 2]</span><br></div><div dir="auto"><span style="font-family:sans-serif">assert b == [1, 2, 3]</span></div><span class=""><div dir="auto"><span style="font-family:sans-serif"><br></span></div><div class="gmail_extra" dir="auto"><div class="gmail_quote"><blockquote class="m_8604034916003579423quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><div class="gmail_quote"><div dir="auto">Another problem is that MutableMapping defines a __delitem__ method, which i don't want the Context to implement.</div></div></div></blockquote></div></div><div dir="auto"><br></div></span><div dir="auto">I wouldn't be shocked if "del ctx [var]" would raise an exception.</div><div dir="auto"><br></div><div dir="auto">I almost never use del anyway. I prefer to assign a variable to None, since "del var" looks like C++ destructor whereas it's more complex than a direct call to the destructor.</div><div dir="auto"><br></div><div dir="auto">But it's annoying to have to call a function with Context.run() whereas context is just a mutable mapping. It seems overkill to me to have to call run() to modify a context variable: run() changes temporarely the context and requires to use the indirect ContextVar API, while I know that ContextVar.set() modifies the context.</div><div dir="auto"><br></div><div dir="auto">Except of del corner case, I don't see any technical reason to prevent direct modification of a context.</div><div dir="auto"><br></div><div dir="auto">contextvars isn't new, it extends what we already have: decimal context. And decimal quick start documentation shows how to modify a context and then set it as the current context:</div><div dir="auto"><br></div><div dir="auto"><div dir="auto">>>> myothercontext = Context(prec=60, rounding=ROUND_HALF_DOWN)</div><div dir="auto">>>> setcontext(myothercontext)</div><div dir="auto">>>> Decimal(1) / Decimal(7)</div><div dir="auto">Decimal('0.<wbr>142857142857142857142857142857<wbr>142857142857142857142857142857<wbr>')</div><div dir="auto"><br></div></div><div dir="auto"><a href="https://docs.python.org/dev/library/decimal.html" target="_blank">https://docs.python.org/dev/<wbr>library/decimal.html</a><br></div><div dir="auto"><br></div><div dir="auto"><div dir="auto" style="font-family:sans-serif">Well, technically it doesn't modify a context. An example closer to contextvars would be:</div><br></div><div dir="auto"><div dir="auto" style="font-family:sans-serif">>>> mycontext = getcontext().copy()</div><div dir="auto" style="font-family:sans-serif">>>> mycontext.prec = 60</div><div dir="auto" style="font-family:sans-serif">>>> setcontext(mycontext)</div><div dir="auto" style="font-family:sans-serif">>>> Decimal(1) / Decimal(7)</div><div dir="auto" style="font-family:sans-serif">Decimal('0.<wbr>142857142857142857142857142857<wbr>142857142857142857142857142857<wbr>')</div><br></div><div dir="auto">Note: "getcontext().prec = 6" does modify the decimal context directly, and it's the *first* example in the doc. But here contextvars is different since there is no API to get the current API. The lack of API to access directly the current contextvars context is the main difference with decimal context, and I'm fine with that.</div><div dir="auto"><br></div><div dir="auto">It's easy to see a parallel since decimal context can be copied using Context.copy(), it has also multiple (builtin) "variables", it's just that the API is different (decimal context variables are modified as attributes), and i<span style="font-family:sans-serif">t's possible to set a context using decimal.setcontext().</span></div><span class="HOEnZb"><font color="#888888"><div dir="auto"><span style="font-family:sans-serif"><br></span></div><div dir="auto"><span style="font-family:sans-serif">Victor</span></div></font></span></div>
</blockquote></div><br><br clear="all"><br>-- <br><div class="gmail_signature" data-smartmail="gmail_signature">--Guido van Rossum (<a href="http://python.org/~guido" target="_blank">python.org/~guido</a>)</div>
</div>