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@gmail.com | Brisbane, Australia --------------------------------------------------------------- http://www.boredomandlaziness.org