A "scopeguard" for Python
Alf P. Steinbach
alfps at start.no
Wed Mar 3 16:35:09 EST 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