It seems that the following idea has not been raised here before. If it has been, I'd be grateful for a pointer to the relevant discussion.
The idea is: how about adding context manager interface (__enter__ and __exit__ methods) to the contextvars.Token type? This would allow code like this: with var.set(1): # var.get() == 1 that would be equivalent to the following more verbose snippet (taken from PEP 567): token = var.set(1) try: # var.get() == 1 finally: var.reset(token) I attach a very rough proof-of-concept implementation of the idea. The proper way to implement this proposal would be of course to modify the _contextvars C-extension. Here is my motivation for this proposal: The contextvars module is promoted as a replacement for thread-local storage for asynchronous programming, but in fact it seems to me that ContextVars implement thread-safe and async-safe dynamic scoping [1] in Python. One of the uses of dynamic scoping is as an alternative to function parameters for library configuration [2]. While dynamic scoping by default (as in Emacs Lisp) can be dangerous, I believe that explicit dynamic scoping could be useful in the Python world as a means to avoid the "parameter hell" of some libraries, perhaps most infamously demonstrated by matplotlib. With the updated ContextVars, a hypothetical plotting library could be used like this: with plotting.line_thickness.set(2), plotting.line_style('dashed'): plotting.plot(x, y) As explained in [2], the advantages of this approach compared to argument passing is that other functions that internally use plotting.plot do not have to expose all of its configuration options by themselves as parameters. Also, with the same mechanism it is possible to set a parameter for a single invocation of plotting.plot, or a default value for a whole script. [1] https://en.wikipedia.org/wiki/Scope_(computer_science)#Dynamic_scoping [2] https://www.gnu.org/software/emacs/emacs-paper.html#SEC18
from contextvars import ContextVar # Define a work-alike to ContextVar that piggybacks on it. _NO_DEFAULT = '_NO_DEFAULT' class CMContextVar: def __init__(self, name, *, default=_NO_DEFAULT): if default is _NO_DEFAULT: self._cv = ContextVar(name) else: self._cv = ContextVar(name, default=default) def set(self, value): return CMToken(self._cv.set(value)) def get(self): return self._cv.get() def reset(self, token): return self._cv.reset(token._token) class CMToken: def __init__(self, token): self._token = token # The new bits: context manager interface to CMToken. def __enter__(self): pass def __exit__(self, extype, exval, tb): self._token.var.reset(self._token) def test(): var = CMContextVar('var', default=0) print(var.get()) # Demonstrate old interface. token = var.set(1) print(var.get()) var.reset(token) print(var.get()) # Demonstrate new proposed interface. with var.set(2): print(var.get()) print(var.get()) if __name__ == '__main__': test()
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org %(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s Code of Conduct: http://python.org/psf/codeofconduct/