[Python-Dev] Allow enter() methods to skip the with statement body? (original) (raw)

Nick Coghlan ncoghlan at gmail.com
Wed Feb 25 22:28:58 CET 2009


Steven Bethard wrote:

If the problem is just the yield, can't this just be fixed by implementing contextlib.nested() as a class rather than as a @contextmanager decorated generator? Or is this a problem with class based context managers too?

It's a problem for class-based context managers as well. Setting aside the difficulties of actually maintaining nested()'s state on a class rather than in a frame (it's definitely possible, but also somewhat painful), you still end up in the situation where nested() knows that cmB().enter() threw an exception that was then handled by cmA().exit() and hence the body of the with statement should be skipped but no exception should occur from the point of view of the surrounding code. However, indicating that is not currently an option available to nested().enter(): it can either raise an exception (thus skipping the body of the with statement, but also propagating the exception into the surrounding code), or it can return a value (which would lead to the execution of the body of the with statement).

Returning a value would definitely be wrong, but raising the exception isn't really right either.

contextmanager is just a special case - the "skipped yield" inside the generator reflects the body of the with statement being skipped in the original non-context manager code.

As to Brett's question of whether or not this is necessary/useful... the problem I really have with the status quo is that it is currently impossible to look at the following code snippets and say whether or not the created CM's are valid:

cm = contextlib.nested(cmA(), cmB())

@contextlib.contextmanager def cm(): with cmA(): with cmB(): yield

Not tested, probably have the class version wrong

This should illustrate why nested() wasn't written

as a class-based CM though - this one only nests

two specifically named CMs and look how tricky it gets!

class CM(object): def init(self): self.cmA = None self.cmB = None def enter(self): if self.cmA is not None: raise RuntimeError("Can't re-use this CM") self.cmA = cmA() self.cmA.enter() try: self.cmB = cmB() self.cmB.enter() except: self.cmA.exit(*sys.exc_info()) # Can't suppress in enter(), so must raise raise def exit(self, *args): suppress = False try: if self.cmB is not None: suppress = self.cmB.exit(*args) except: suppress = self.cmA.exit(*sys.exc_info()): if not suppress: # Exception has changed, so reraise explicitly raise else: if suppress: # cmB already suppressed the exception, # so don't pass it to cmA suppress = self.cmA.exit(None, None, None): else: suppress = self.cmA.exit(*args): return suppress

With the current with statement semantics, those CM's may raise exceptions where the original multiple with statement code would work fine.

Cheers, Nick.

-- Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia



More information about the Python-Dev mailing list