From: Alf P. Steinbach on
For C++ Petru Marginean once invented the "scope guard" technique (elaborated on
by Andrei Alexandrescu, they published an article about it in DDJ) where all you
need to do to ensure some desired cleanup at the end of a scope, even when the
scope is exited via an exception, is to declare a ScopeGuard w/desired action.

The C++ ScopeGuard was/is for those situations where you don't have proper
classes with automatic cleanup, which happily is seldom the case in good C++
code, but languages like Java and Python don't support automatic cleanup and so
the use case for something like ScopeGuard is ever present.

For use with a 'with' statement and possibly suitable 'lambda' arguments:


<code>
class Cleanup:
def __init__( self ):
self._actions = []

def call( self, action ):
assert( is_callable( action ) )
self._actions.append( action )

def __enter__( self ):
return self

def __exit__( self, x_type, x_value, x_traceback ):
while( len( self._actions ) != 0 ):
try:
self._actions.pop()()
except BaseException as x:
raise AssertionError( "Cleanup: exception during cleanup" ) from
</code>


I guess the typical usage would be what I used it for, a case where the cleanup
action (namely, changing back to an original directory) apparently didn't fit
the standard library's support for 'with', like

with Cleanup as at_cleanup:
# blah blah
chdir( somewhere )
at_cleanup.call( lambda: chdir( original_dir ) )
# blah blah

Another use case might be where one otherwise would get into very deep nesting
of 'with' statements with every nested 'with' at the end, like a degenerate tree
that for all purposes is a list. Then the above, or some variant, can help to
/flatten/ the structure. To get rid of that silly & annoying nesting. :-)


Cheers,

- Alf (just sharing, it's not seriously tested code)
From: Mike Kent on
What's the compelling use case for this vs. a simple try/finally?

original_dir = os.getcwd()
try:
os.chdir(somewhere)
# Do other stuff
finally:
os.chdir(original_dir)
# Do other cleanup
From: Alf P. Steinbach on
* Mike Kent:
> What's the compelling use case for this vs. a simple try/finally?

if you thought about it you would mean a simple "try/else". "finally" is always
executed. which is incorrect for cleanup

by the way, that's one advantage:

a "with Cleanup" is difficult to get wrong, while a "try" is easy to get wrong,
as you did here


---

another general advantage is as for the 'with' statement generally



> original_dir = os.getcwd()
> try:
> os.chdir(somewhere)
> # Do other stuff

also, the "do other stuff" can be a lot of code

and also, with more than one action the try-else introduces a lot of nesting


> finally:
> os.chdir(original_dir)
> # Do other cleanup


cheers & hth.,

- alf
From: Robert Kern on
On 2010-03-03 09:39 AM, Mike Kent wrote:
> What's the compelling use case for this vs. a simple try/finally?
>
> original_dir = os.getcwd()
> try:
> os.chdir(somewhere)
> # Do other stuff
> finally:
> os.chdir(original_dir)
> # Do other cleanup

A custom-written context manager looks nicer and can be more readable.

from contextlib import contextmanager
import os

@contextmanager
def pushd(path):
original_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(original_dir)


with pushd(somewhere):
...


I don't think a general purpose ScopeGuard context manager has any such benefits
over the try: finally:, though.

--
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

From: Alf P. Steinbach on
* Robert Kern:
> On 2010-03-03 09:39 AM, Mike Kent wrote:
>> What's the compelling use case for this vs. a simple try/finally?
>>
>> original_dir = os.getcwd()
>> try:
>> os.chdir(somewhere)
>> # Do other stuff
>> finally:
>> os.chdir(original_dir)
>> # Do other cleanup
>
> A custom-written context manager looks nicer and can be more readable.
>
> from contextlib import contextmanager
> import os
>
> @contextmanager
> def pushd(path):
> original_dir = os.getcwd()
> os.chdir(path)
> try:
> yield
> finally:
> os.chdir(original_dir)
>
>
> with pushd(somewhere):
> ...
>
>
> I don't think a general purpose ScopeGuard context manager has any such
> benefits over the try: finally:, though.

I don't think that's a matter of opinion, since one is correct while the other
is incorrect.


Cheers,

- ALf