A "scopeguard" for Python

Robert Kern robert.kern at gmail.com
Fri Mar 5 12:00:22 EST 2010


On 2010-03-05 10:29 AM, Mike Kent wrote:
> On Mar 4, 8:04 pm, Robert Kern<robert.k... at gmail.com>  wrote:
>
>> No, the try: finally: is not implicit. See the source for
>> contextlib.GeneratorContextManager. When __exit__() gets an exception from the
>> with: block, it will push it into the generator using its .throw() method. This
>> raises the exception inside the generator at the yield statement.
>
> Wow, I just learned something new.  My understanding of context
> managers was that the __exit__ method was guaranteed to be executed
> regardless of how the context was left.

It is. @contextmanager turns a specially-written generator into a context 
manager with an __exit__ that does different things depending on whether or not 
and exception was raised. By pushing the exception into the generator, it lets 
the author decide what to do. It may catch a subset of exceptions, or no 
exceptions, or use a finally:. They all have use cases although finally: is the 
usual one.

> I have often written my own
> context manager classes, giving them the __enter__ and __exit__
> methods.  I had mistakenly assumed that the @contextmanager decorator
> turned a generator function into a context manager with the same
> behavior as the equivalent context manager class.

Basically, it does. __exit__() is given the exception information. When you 
write such a class, you can decide what to do with the exception. You can 
silence it, immediately reraise it, conditionally reraise it, log it and then 
reraise it, etc. Pushing the exception into the generator keeps this flexibility 
and the equivalence. If it removed that choice, then it would not be equivalent.

> Now I learn that,
> no, in order to have the 'undo' code executed in the presence of an
> exception, you must write your own try/finally block in the generator
> function.
>
> This raises the question in my mind: What's the use case for using
> @contextmanager rather than wrapping your code in a context manager
> class that defines __enter__ and __exit__, if you still have to
> manager your own try/finally block?

The @contextmanager generator implementations are often shorter and easier to 
read, in my opinion, partly because they use the try: finally: syntax that most 
of us are very familiar with. I have to think less when I read it because it 
looks so similar to the equivalent code that you would normally write.

The point of context managers isn't to remove the use of try: finally: entirely, 
but to implement it once so that it can be reused cleanly. You only have to 
write the one try: finally: in the generator and reuse it simply with the with: 
statement in many places.

-- 
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
  that is made terrible by our own mad attempt to interpret it as though it had
  an underlying truth."
   -- Umberto Eco




More information about the Python-list mailing list