* Mike Kent:
On Mar 4, 8:04 pm, Robert Kern <robert.k...@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.  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.  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?

Robert Kern and Steve Howell have already given given good answers.

As it happened this was news to me also, because I'm not that well-versed in Python and it seems contrary to the purpose of providing a simpler way to write a simple init-cleanup wrapper.

But additionally, if you want that, then you can define it, e.g.


<code>
# Py3

def simplecleanup( generator_func ):
    class SimpleCleanup:
        def __init__( self, *args, **kwargs ):
            self.generator = generator_func( *args, **kwargs )

        def __enter__( self ):
            self.generator.send( None )
            return self

        def __exit__( self, x_type, x_obj, x_traceback ):
            try:
                self.generator.send( x_obj )    # x_obj is None if no exception
            except StopIteration:
                pass                            # Expected

    return SimpleCleanup


@simplecleanup
def hello_goodbye( name ):
    print( "Hello, {}!".format( name ) )
    yield
    print( "Goodbye {}!".format( name ) )


try:
    with hello_goodbye( "Mary" ):
        print( "Talk talk talk..." )
        raise RuntimeError( "Offense" )
except:
    pass
print()


@simplecleanup
def sensitive_hello_goodbye( name ):
    print( "Hello, {}!".format( name ) )
    x = yield
    if x is not None:
        print( "Uh oh, {}!".format( x ) )
        print( "Good day {}!".format( name ) )
    else:
        print( "C u, {}!".format( name ) )


try:
    with sensitive_hello_goodbye( "Jane" ):
        print( "Talk talk talk..." )
        raise RuntimeError( "Offense" )
except:
    pass
</code>


Cheers,

- Alf
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to