A "scopeguard" for Python

Alf P. Steinbach alfps at start.no
Fri Mar 5 14:34:00 EST 2010


* Steve Howell:
> On Mar 3, 7:10 am, "Alf P. Steinbach" <al... at start.no> wrote:
>> 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)
> 
> Hi Alf, I think I understand the notion you're going after here.  You
> have multiple cleanup steps that you want to defer till the end, and
> there is some possibility that things will go wrong along the way, but
> you want to clean up as much as you can.  And, of course, flatter is
> better.
> 
> Is this sort of what you are striving for?
> 
>     class Cleanup:
>          def __init__( self ):
>              self._actions = []
> 
>          def call( self, 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" )
> 
>     def clean_the_floor():
>         print('clean the floor')
> 
>     def carouse(num_bottles, accident):
>         with Cleanup() as at_cleanup:
>             at_cleanup.call(clean_the_floor)
>             for i in range(num_bottles):
>                 def take_down(i=i):
>                     print('take one down', i)
>                 at_cleanup.call(take_down)
>                 if i == accident:
>                     raise Exception('oops!')
>                 print ('put bottle on wall', i)
> 
>     carouse(10, None)
>     carouse(5, 3)

He he.

I think you meant:

 >     def carouse(num_bottles, accident):
 >         with Cleanup() as at_cleanup:
 >             at_cleanup.call(clean_the_floor)
 >             for i in range(num_bottles):
 >                 def take_down(i=i):
 >                     print('take one down', i)
 >                 if i == accident:
 >                     raise Exception('oops!')
 >                 print ('put bottle on wall', i)
 >                 at_cleanup.call(take_down)

I'm not sure. It's interesting & fun. But hey, it's Friday evening.

Regarding the "clean the floor", Marginean's original ScopeGuard has a 'dismiss' 
method (great for e.g. implementing transactions). There's probably also much 
other such functionality that can be added.

The original use case for Cleanup, I just posted this in case people could find 
it useful, was a harness for testing that C++ code /fails/ as it should,
<url: http://pastebin.com/NK8yVcyv>, where Cleanup is used at line 479.

Some discussion of that in Usenet message and associated thread 
<hmmcdm$p1i$1 at news.eternal-september.org>, "Unit testing of expected failures -- 
what do you use?" in [comp.lang.c++].

Another similar approach was discussed by Carlo Milanesi in <url: 
http://www.drdobbs.com/cpp/205801074>, but he supplied this reference after I'd 
done the above. Mainly the difference is that he defines a custom mark-up 
language with corresponding source preprocessing, while I use the ordinary C++ 
preprocessor. There are advantages and disadvantages to both approaches.


Cheers,

- Alf



More information about the Python-list mailing list