A "scopeguard" for Python

Alf P. Steinbach alfps at start.no
Wed Mar 3 14:32:25 EST 2010


* Robert Kern:
> On 2010-03-03 11:18 AM, Alf P. Steinbach wrote:
>> * Robert Kern:
>>> On 2010-03-03 09:56 AM, Alf P. Steinbach wrote:
>>>> * 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
>>>
>>> Eh? Failed execution doesn't require cleanup? The example you gave is
>>> definitely equivalent to the try: finally: that Mike posted.
>>
>> Sorry, that's incorrect: it's not.
>>
>> With correct code (mine) cleanup for action A is only performed when
>> action A succeeds.
>>
>> With incorrect code cleanup for action A is performed when A fails.
> 
> Oh?
> 
> $ cat cleanup.py
> 
> class Cleanup:
>     def __init__( self ):
>         self._actions = []
> 
>     def call( self, action ):
>         assert( 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" )
> 
> def print_(x):
>     print x
> 
> with Cleanup() as at_cleanup:
>     at_cleanup.call(lambda: print_("Cleanup executed without an 
> exception."))
> 
> with Cleanup() as at_cleanup:

*Here* is where you should

   1) Perform the action for which cleanup is needed.

   2) Let it fail by raising an exception.


>     at_cleanup.call(lambda: print_("Cleanup execute with an exception."))
>     raise RuntimeError()

With an exception raised here cleanup should of course be performed.

And just in case you didn't notice: the above is not a test of the example I gave.


> $ python cleanup.py
> Cleanup executed without an exception.
> Cleanup execute with an exception.
> Traceback (most recent call last):
>   File "cleanup.py", line 28, in <module>
>     raise RuntimeError()
> RuntimeError
> 
>>> The actions are always executed in your example,
>>
>> Sorry, that's incorrect.
> 
> Looks like it to me.

I'm sorry, but you're

  1) not testing my example which you're claiming that you're testing, and

  2) not even showing anything about your earlier statements, which were
     just incorrect.

You're instead showing that my code works as it should for the case that you're 
testing, which is a bit unnecessary since I knew that, but thanks anyway.

I'm not sure what that shows, except that you haven't grokked this yet.


>>> From your post, the scope guard technique is used "to ensure some
>>> desired cleanup at the end of a scope, even when the scope is exited
>>> via an exception." This is precisely what the try: finally: syntax is
>>> for.
>>
>> You'd have to nest it. That's ugly. And more importantly, now two people
>> in this thread (namely you and Mike) have demonstrated that they do not
>> grok the try functionality and manage to write incorrect code, even
>> arguing that it's correct when informed that it's not, so it's a pretty
>> fragile construct, like goto.
> 
> Uh-huh.

Yeah. Consider that you're now for the third time failing to grasp the concept 
of cleanup for a successful operation.


>>> The with statement allows you to encapsulate repetitive boilerplate
>>> into context managers, but a general purpose context manager like your
>>> Cleanup class doesn't take advantage of this.
>>
>> I'm sorry but that's pretty meaningless. It's like: "A house allows you
>> to encapsulate a lot of stinking garbage, but your house doesn't take
>> advantage of that, it's disgustingly clean". Hello.
> 
> No, I'm saying that your Cleanup class is about as ugly as the try: 
> finally:. It just shifts the ugliness around. There is a way to use the 
> with statement to make things look better and more readable in certain 
> situations, namely where there is some boilerplate that you would 
> otherwise repeat in many places using try: finally:. You can encapsulate 
> that repetitive code into a class or a @contextmanager generator and 
> just call the contextmanager. A generic context manager where you 
> register callables doesn't replace any boilerplate. You still repeat all 
> of the cleanup code everywhere. What's more, because you have to shove 
> everything into a callable, you have significantly less flexibility than 
> the try: finally:.

Sorry, but that's meaningless again. You're repeating that my house has no 
garbage in it. And you complain that it would be work to add garbage to it. Why 
do you want that garbage? I think it's nice without it!


> I will admit that you can put the cleanup code closer to the code that 
> needs to get cleaned up, but you pay a price for that.

Yes, that's an additional point, and important. I forgot to mention it. Thanks!


Cheers & hth.,

- Alf



More information about the Python-list mailing list