On 2010-03-03 18:49 PM, Alf P. Steinbach wrote:
* Robert Kern:
On 2010-03-03 15:35 PM, Alf P. Steinbach wrote:
* 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.

Okay, but just because of the position of the chdir(), right?

Yes, since it yields different results.

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?

I'm just showing you what I thought you meant when you told Mike that
he should have used a try/else instead of try/finally.

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>

Ah, okay. Now we're getting somewhere. Now, please note that you did
not have any except: handling in your original example. So Mike made a
try: finally: example to attempt to match the semantics of your code.
When you tell him that he should 'mean a simple "try/else". "finally"
is always executed. which is incorrect for cleanup', can you
understand why we might think that you were saying that try: finally:
was wrong and that you were proposing that your code was equivalent to
some try: except: else: suite?

No, not really. His code didn't match the semantics. Changing 'finally'
to 'else' could make it equivalent.

Okay, please show me what you mean by "changing 'finally' to 'else'." I think you are being hinty again. It's not helpful. The most straightforward interpretation of those words means that you literally just want to remove the word 'finally' and replace it with 'else' in Mike's example. Obviously you don't mean that because it is a syntax error. try: else: is not a construct in Python. There is a try: except: else:, but there is no point to doing that if you don't have anything in the except: clause. Neither Mike's example nor your original one have any except: clause. Why do you think that we would interpret those words to mean that you wanted the example you give just above?

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?

I say "seems" because my understandings of what you meant in your
original post and your response to Mike disagreed with one another.
Now I see that your later posts were talking about minor discrepancy
about which errors you wanted caught by the finally: and which you
didn't.

It's absolutely not a minor discrepancy whether some code is executed or
not. It can have arbitrarily large effect. And from my point of view the
discussion of that snippet has not been about what errors I "want"
caught by the 'finally'; it's been about whether two snippets of code
yield the same effect or not: Mike's code was incorrect not because it
did something else, but because as code that did something else it was
not an equivalent to the code that I posted.


I was confused because it seemed that you were saying that try:
finally: was completely wrong and that "try/else" was right. It
confused me and at least one other person.

, 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.

No, I'm just trying to clarify what you are trying to say. The above
statement did not appear to accord with your later statement: 'if you
thought about it you would mean a simple "try/else". "finally" is
always executed. which is incorrect for cleanup.' It turns out that
what you really meant was that it would be incorrect for cleanup to be
executed when an error occurred in the chdir() itself.

Now, I happen to disagree with that.

Well, I was pretty unclear, almost hint-like, sorry about that, mea
culpa, but you have it slightly wrong. You wrote then "The example you
gave is definitely equivalent to the try: finally: that Mike posted."
And it isn't.

I agree that it does behave differently with respect to when an exception is raised in chdir(). I was wrong on that point. I thought you were claiming that it behaved differently when there was an exception in the "# Do other stuff" block because you were being (and are still being) unclear.

There are a couple of ways to do this kind of cleanup depending on the
situation. Basically, you have several different code blocks:

# 1. Record original state.
# 2. Modify state.
# 3. Do stuff requiring the modified state.
# 4. Revert to the original state.

Depending on where errors are expected to occur, and how the state
needs to get modified and restored, there are different ways of
arranging these blocks. The one Mike showed:

# 1. Record original state.
try:
# 2. Modify state.
# 3. Do stuff requiring the modified state.
finally:
# 4. Revert to the original state.

And the one you prefer:

# 1. Record original state.
# 2. Modify state.
try:
# 3. Do stuff requiring the modified state.
finally:
# 4. Revert to the original state.

These differ in what happens when an error occurs in block #2, the
modification of the state. In Mike's, the cleanup code runs; in yours,
it doesn't. For chdir(), it really doesn't matter. Reverting to the
original state is harmless whether the original chdir() succeeds or
fails, and chdir() is essentially atomic so if it raises an exception,
the state did not change and nothing needs to be cleaned up.

However, not all block #2s are atomic. Some are going to fail partway
through and need to be cleaned up even though they raised an
exception. Fortunately, cleanup can frequently be written to not care
whether the whole thing finished or not.

Yeah, and there are some systematic ways to handle these things. You
might look up Dave Abraham's levels of exception safety. Mostly his
approach boils down to making operations effectively atomic so as to
reduce the complexity: ideally, if an operation raises an exception,
then it has undone any side effects.

Of course it can't undo the launching of an ICBM, for example...

But ideally, if it could, then it should.

I agree. Atomic operations like chdir() help a lot. But this is Python, and exceptions can happen in many different places. If you're not just calling an extension module function that makes a known-atomic system call, you run the risk of not having an atomic operation.

If you call the possibly failing operation "A", then that systematic
approach goes like this: if A fails, then it has cleaned up its own
mess, but if A succeeds, then it's the responsibility of the calling
code to clean up if the higher level (multiple statements) operation
that A is embedded in, fails.

And that's what Marginean's original C++ ScopeGuard was designed for,
and what the corresponding Python Cleanup class is designed for.

And try: finally:, for that matter.

Both formulations can be correct (and both work perfectly fine with
the chdir() example being used). Sometimes one is better than the
other, and sometimes not. You can achieve both ways with either your
Cleanup class or with try: finally:.

I am still of the opinion that Cleanup is not an improvement over try:
finally: and has the significant ugliness of forcing cleanup code into
callables. This significantly limits what you can do in your cleanup
code.

Uhm, not really. :-) As I see it.

Well, not being able to affect the namespace is a significant limitation. Sometimes you need to delete objects from the namespace in order to ensure that their refcounts go to zero and their cleanup code gets executed. Tracebacks will keep the namespace alive and all objects in it.

--
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
 that is made terrible by our own mad attempt to interpret it as though it had
 an underlying truth."
  -- Umberto Eco

--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to