* Steve Howell:
On Mar 3, 7:10 am, "Alf P. Steinbach" <al...@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$p1...@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
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to