A "scopeguard" for Python

Alf P. Steinbach alfps at start.no
Wed Mar 3 22:35:09 CET 2010


* Robert Kern:
> On 2010-03-03 13:32 PM, Alf P. Steinbach wrote:
>> * 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
> 
> Then I would appreciate your writing a complete, runnable example that 
> demonstrates the feature you are claiming. Because it's apparently not 
> "ensur[ing] some desired cleanup at the end of a scope, even when the 
> scope is exited via an exception" that you talked about in your original 
> post.
> 
> Your sketch of an example looks like mine:
> 
>   with Cleanup as at_cleanup:
>       # blah blah
>       chdir( somewhere )
>       at_cleanup.call( lambda: chdir( original_dir ) )
>       # blah blah
> 
> The cleanup function gets registered immediately after the first chdir() 
> and before the second "blah blah". Even if an exception is raised in the 
> second "blah blah", then the cleanup function will still run. This would 
> be equivalent to a try: finally:
> 
> # blah blah #1
> chdir( somewhere )
> try:
>     # blah blah #2
> finally:
>     chdir( original_dir )

Yes, this is equivalent code.

The try-finally that you earlier claimed was equivalent, was not.



> and not a try: else:
> 
> # blah blah #1
> chdir( somewhere )
> try:
>     # blah blah #2
> else:
>     chdir( original_dir )

This example is however meaningless except as misdirection. There are infinitely 
many constructs that include try-finally and try-else, that the with-Cleanup 
code is not equivalent to. It's dumb to show one such.

Exactly what are you trying to prove here?

Your earlier claims are still incorrect.


> Now, I assumed that the behavior with respect to exceptions occurring in 
> the first "blah blah" weren't what you were talking about because until 
> the chdir(), there is nothing to clean up.
> 
> There is no way that the example you gave translates to a try: else: as 
> you claimed in your response to Mike Kent.

Of course there is.

Note that Mike wrapped the action A within the 'try':


<code author="Mike" correct="False">
    original_dir = os.getcwd()
    try:
        os.chdir(somewhere)
        # Do other stuff
    finally:
        os.chdir(original_dir)
        # Do other cleanup
</code>


The 'finally' he used, shown above, yields incorrect behavior.

Namely cleanup always, while 'else', in that code, can yield correct behavior 
/provided/ that it's coded correctly:


<code author="Alf" correct="ProbablyTrue" disclaimer="off the cuff">
    original_dir = os.getcwd()
    try:
        os.chdir(somewhere)
    except Whatever:
        # whatever, e.g. logging
        raise
    else:
        try:
            # Do other stuff
        finally:
            os.chdir(original_dir)
            # Do other cleanup
</code>


>> 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.
> 
> It's the case you seem to be talking about in your original post.

What's this "seems"? Are you unable to read that very short post?


> You 
> seem to have changed your mind about what you want to talk about. That's 
> fine.

And what's this claim about me changing any topic?


> We don't have to stick with the original topic

Why not stick with the original topic?


>, but I do ask you 
> to acknowledge that you originally were talking about a feature that 
> "ensure[s] some desired cleanup at the end of a scope, even when the 
> scope is exited via an exception."

Yes, that's what it does.

Which is I why I wrote that.

This should not be hard to grok.


> Do you acknowledge this?

This seems like pure noise, to cover up that you were sputing a lot of incorrect 
statements earlier.


>> 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.
> 
> Oh, I do. But if I didn't want it to run on an exception, I'd just write 
> the code without any try:s or with:s at all.
> 
> # blah blah #1
> chdir( somewhere )
> # blah blah #2
> chdir( original_dir )

Yes, but what's that got to do with anything?


>>>>> 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.
> 
> No, I'm repeatedly saying that I think your solution stinks. I think 
> it's ugly. I think it's restrictive. I think it does not improve on the 
> available solutions.

First you'd have to understand it, simple as it is.


>> 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!
> 
> And you are entitled to that opinion. I am giving you mine.

Well, I'm sorry, but while you are entitled to an opinion it's not an opinion 
that carries any weight: first you need to get your facts and claims straight.

So far only this latest posting of yours has been free of directly incorrect 
statements.

But you still have a lot of statements that just show total incomprehension, 
like your example of achieving no cleanup in the case of an exception.


Cheers & hth.,

- Alf



More information about the Python-list mailing list