[Python-Dev] More on contextlib - adding back a contextmanager decorator
Nick Coghlan
ncoghlan at gmail.com
Mon May 1 04:05:15 CEST 2006
Guido van Rossum wrote:
> On 4/30/06, Nick Coghlan <ncoghlan at iinet.net.au> wrote:
>> A few things from the pre-alpha2 context management terminology review
>> have
>> had a chance to run around in the back of my head for a while now, and
>> I'd
>> like to return to a topic Paul Moore brought up during that discussion.
>
> I believe the context API design has gotten totally out of hand.
> Regardless of the merits of the "with" approach to HTML generation
> (which I personally believe to be an abomination),
The example is tempting because it's easy to follow. I agree actually doing it
in real code would almost certainly be nuts :)
> I don't see why the
> standard library should support every possible use case with a
> custom-made decorator. Let the author of that tag library provide the
> decorator.
The HTML tag was just an example. The underlying idea is being able to easily
create a re-usable object that can be passed to multiple with statements
(potentially nested within each other or within distinct threads). Without the
__context__ method, the naive version of such an object looks like:
class reusable(object):
def __init__(self, factory):
self.factory = factory
factory() # Check the factory works at definition time
def __enter__(self):
current = self.current = factory()
return current.__enter__()
def __exit__(self, *exc_info):
return self.current.__exit__(*exc_info)
The downside of this over the __context__ method is that it is neither nesting
nor thread-safe. Because the storage is on the object rather than in the
execution frame, sharing such objects between threads or using one for nested
with statements will break (as self.current gets overwritten).
> I have a counter-proposal: let's drop __context__. Nearly all use
> cases have __context__ return self. In the remaining cases, would it
> really be such a big deal to let the user make an explicit call to
> some appropriately named method? The only example that I know of where
> __context__ doesn't return self is the decimal module.
It would also prevent threading.Condition from using its underlying lock
object as the managed context.
The real problem I have with removing __context__() is that it pushes the
burden of handling thread-safety and nesting-safety issues onto the developers
of context managers without giving them any additional tools beyond
threading.locals(). This was the problem Jason brought up for decimal.Context
that lead to the introduction of __context__ in the first place.
Without the __context__() method, *users* of the with statement will be forced
to create a new object with __enter__()/__exit__() methods every time, either
by invoking a method (whose name will vary from object to object, depending on
the whim of the designer) or by calling a factory function (which is likely to
be created either as a zero-argument lambda returning an object with
enter/exit methods, or else by using PEP 309's partial function).
So if you see a with statement with a bare variable name as the context
expression, it will probably be wrong, unless:
a) the implementor of that type provided thread-safety and nesting-safety; or
b) the object is known to be neither thread-safe nor nesting-safe
The synchronisation objects in threading being examples of category a, file
objects being examples of category b. In this scenario, generator contexts
defined using @contextfactory should always be invoked directly in the context
expression, as attempting to cache them in order to be reused won't work (you
would need to put them in a zero-argument lambda and call it in the context
expression, so that you get a new generator object each time).
Documenting all of the thread-safety and nesting-safety issues and how to deal
with them would be a serious pain. I consider it much easier to provide the
__context__() method and explain how to use that as the one obvious way to
deal with such problems. Then only implementors need to care about it - from a
user's point of view, you just provide a context expression that resolves to a
context manager, and everything works as intended, including being able to
cache that expression in a local variable and use it multiple times. (That
last point obviously not applying to context managers like files that leave
themselves in an unusable state after __exit__, and don't restore themselves
to a usable state in __enter__).
Essentially, I don't think dropping __context__ would gain us anything - the
complexity associated with it is real, and including that method in the API
let's us deal with that complexity in one place, once and for all.
Removing the method from the statement definition just pushes the
documentation burden out to all of the context managers where it matters (like
decimal.Context, the documentation for which would get stuck with trying to
explain why you have to call a method in order to get a usable context manager).
Cheers,
Nick.
--
Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia
---------------------------------------------------------------
http://www.boredomandlaziness.org
More information about the Python-Dev
mailing list