[Python-Dev] More on contextlib - adding back a contextmanager decorator
Nick Coghlan
ncoghlan at iinet.net.au
Sun Apr 30 15:23:46 CEST 2006
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.
Paul had a feeling there should be two generator decorators in contextlib -
one for __context__ methods and one for standalone generator functions.
However, contextfactory seemed to meet both needs, so we didn't follow the
question up for alpha 2.
The second link in this chain is the subsequent discussion with Guido about
making the context manager and managed context protocols orthogonal. With this
clearer separation of the two terms to happen in alpha 3, it becomes more
reasonable to have two different generator decorators, one for defining
managed contexts and one for defining context managers.
The final link is a use case for such a context manager decorator, in the form
of a couple of HTML tag example contexts in the contextlib documentation.
(contextlib.nested is also a good use case, where caching can be used to
ensure certain resources are always acquired in the same order, but the issue
is easier to demonstrate using the HTML tag examples)
Firstly, the class-based HTML tag context manager example:
--------------------
class TagClass:
def __init__(self, name):
self.name = name
@contextfactory
def __context__(self):
print "<%s>" % self.name
yield self
print "</%s>" % self.name
>>> h1_cls = TagClass('h1')
>>> with h1_cls:
... print "Header A"
...
<h1>
Header A
</h1>
>>> with h1_cls:
... print "Header B"
...
<h1>
Header B
</h1>
--------------------
Each with statement creates a new context object, so caching the tag object
itself works just as you would expect. Unfortunately, the same cannot be said
for the generator based version:
--------------------
@contextfactory
def tag_gen(name):
print "<%s>" % name
yield
print "</%s>" % name
>>> h1_gen = tag_gen('h1')
>>> with h1_gen:
... print "Header A"
...
<h1>
Header A
</h1>
>>> with h1_gen:
... print "Header B"
...
Traceback (most recent call last):
...
RuntimeError: generator didn't yield
--------------------
The managed contexts produced by the context factory aren't reusable, so
caching them doesn't work properly - they need to be created afresh for each
with statement.
Adding another decorator to define context managers, as Paul suggested, solves
this problem neatly. Here's a possible implementation:
def managerfactory(gen_func):
# Create a context manager factory from a generator function
context_factory = contextfactory(gen_func)
def wrapper(*args, **kwds):
class ContextManager(object):
def __context__(self):
return context_factory(*args, **kwds)
mgr = ContextManager()
mgr.__context__() # Throwaway context to check arguments
return mgr
# Having @functools.decorator would eliminate the next 4 lines
wrapper.__name__ = context_factory.__name__
wrapper.__module__ = context_factory.__module__
wrapper.__doc__ = context_factory.__doc__
wrapper.__dict__.update(context_factory.__dict__)
return wrapper
@managerfactory
def tag_gen2(name):
print "<%s>" % name
yield
print "</%s>" % name
>>> h1_gen2 = tag_gen2('h1')
>>> with h1_gen2:
... print "Header A"
...
<h1>
Header A
</h1>
>>> with h1_gen2:
... print "Header B"
...
<h1>
Header B
</h1>
--
Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia
---------------------------------------------------------------
http://www.boredomandlaziness.org
More information about the Python-Dev
mailing list