Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

A "scopeguard" for Python

551 views
Skip to first unread message

Alf P. Steinbach

unread,
Mar 3, 2010, 10:10:26 AM3/3/10
to
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)

Mike Kent

unread,
Mar 3, 2010, 10:39:05 AM3/3/10
to
What's the compelling use case for this vs. a simple try/finally?

original_dir = os.getcwd()
try:
os.chdir(somewhere)
# Do other stuff
finally:
os.chdir(original_dir)
# Do other cleanup

Alf P. Steinbach

unread,
Mar 3, 2010, 10:56:25 AM3/3/10
to
* 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

by the way, that's one advantage:

a "with Cleanup" is difficult to get wrong, while a "try" is easy to get wrong,
as you did here


---

another general advantage is as for the 'with' statement generally

> original_dir = os.getcwd()
> try:
> os.chdir(somewhere)
> # Do other stuff

also, the "do other stuff" can be a lot of code

and also, with more than one action the try-else introduces a lot of nesting


> finally:
> os.chdir(original_dir)
> # Do other cleanup


cheers & hth.,

- alf

Robert Kern

unread,
Mar 3, 2010, 12:00:04 PM3/3/10
to pytho...@python.org

A custom-written context manager looks nicer and can be more readable.

from contextlib import contextmanager
import os

@contextmanager
def pushd(path):
original_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(original_dir)


with pushd(somewhere):
...


I don't think a general purpose ScopeGuard context manager has any such benefits
over the try: finally:, though.

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

Alf P. Steinbach

unread,
Mar 3, 2010, 12:09:53 PM3/3/10
to
* Robert Kern:

> On 2010-03-03 09:39 AM, Mike Kent wrote:
>> What's the compelling use case for this vs. a simple try/finally?
>>
>> original_dir = os.getcwd()
>> try:
>> os.chdir(somewhere)
>> # Do other stuff
>> finally:
>> os.chdir(original_dir)
>> # Do other cleanup
>
> A custom-written context manager looks nicer and can be more readable.
>
> from contextlib import contextmanager
> import os
>
> @contextmanager
> def pushd(path):
> original_dir = os.getcwd()
> os.chdir(path)
> try:
> yield
> finally:
> os.chdir(original_dir)
>
>
> with pushd(somewhere):
> ...
>
>
> I don't think a general purpose ScopeGuard context manager has any such
> benefits over the try: finally:, though.

I don't think that's a matter of opinion, since one is correct while the other
is incorrect.


Cheers,

- ALf

Robert Kern

unread,
Mar 3, 2010, 12:09:11 PM3/3/10
to pytho...@python.org
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. The actions are always
executed in your example, not just when an exception isn't raised.

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

Alf P. Steinbach

unread,
Mar 3, 2010, 12:18:44 PM3/3/10
to
* 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.


> The actions
> are always executed in your example,

Sorry, that's incorrect.


> [The actions are] not [executed] just when an exception isn't raised.

Sorry, that's incorrect.


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


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


Cheers & hth.,

- Alf

Robert Kern

unread,
Mar 3, 2010, 1:51:34 PM3/3/10
to pytho...@python.org
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:
at_cleanup.call(lambda: print_("Cleanup execute with an exception."))
raise RuntimeError()

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

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

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

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.

Alf P. Steinbach

unread,
Mar 3, 2010, 2:32:25 PM3/3/10
to

*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!

Jerry Hill

unread,
Mar 3, 2010, 4:02:02 PM3/3/10
to pytho...@python.org
On Wed, Mar 3, 2010 at 2:32 PM, Alf P. Steinbach <al...@start.no> wrote:
> I'm not sure what that shows, except that you haven't grokked this yet.

Maybe you could give us an example of how your code should be used,
and how it differs from the other examples people have given? And
maybe a quick example of why you would not want to clean up after a
failed operation?

I've been trying to follow along, and I don't get it either. I guess
that makes me at least the third person that doesn't understand what
you're trying to get across.

--
Jerry

Robert Kern

unread,
Mar 3, 2010, 4:02:31 PM3/3/10
to pytho...@python.org

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 )

and not a try: else:

# blah blah #1
chdir( somewhere )
try:
# blah blah #2
else:
chdir( original_dir )

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.

> 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. You seem to
have changed your mind about what you want to talk about. That's fine. We don't
have to 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."

Do you acknowledge this?

> 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 )

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

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

Alf P. Steinbach

unread,
Mar 3, 2010, 4:35:09 PM3/3/10
to

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.

Robert Kern

unread,
Mar 3, 2010, 6:28:35 PM3/3/10
to pytho...@python.org

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

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

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?

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

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.

Alf P. Steinbach

unread,
Mar 3, 2010, 7:49:45 PM3/3/10
to

Yes, since it yields different results.

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


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

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.

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.


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

But for any given task one should use the most practical tool, and I'm certainly
not claiming that Cleanup will always be that: it's just another weapon to
employ in the correctness war -- although I think it's a powerful one.


Cheers,

- Alf

Jean-Michel Pichavant

unread,
Mar 4, 2010, 5:50:06 AM3/4/10
to Alf P. Steinbach, pytho...@python.org
Alf P. Steinbach wrote:
>> 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.

You want to execute some cleanup when things go wrong, use try except.
You want to do it when things go right, use try else. You want to
cleanup no matter what happen, use try finally.

There is no need of any Cleanup class, except for some technical
alternative concern.

JM


Alf P. Steinbach

unread,
Mar 4, 2010, 10:48:41 AM3/4/10
to
* Jean-Michel Pichavant:

Have you considered that your argument applies to the "with" construct?

You have probably not realized that.

But let me force it on you: when would you use "with"?

Check if that case is covered by your argument above.

Now that you've been told about the "with" angle, don't you think it's a kind of
weakness in your argument that it calls for removing "with" from the language?

I recommend that you think about why your argument is invalid.

Or, as I like to say, why your argument is completely bogus.

Robert Kern

unread,
Mar 4, 2010, 10:48:41 AM3/4/10
to pytho...@python.org

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?

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.

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

unread,
Mar 4, 2010, 11:04:02 AM3/4/10
to pytho...@python.org
On 2010-03-04 09:48 AM, Alf P. Steinbach wrote:
> * Jean-Michel Pichavant:
>> Alf P. Steinbach wrote:
>>>> 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.
>>
>> You want to execute some cleanup when things go wrong, use try except.
>> You want to do it when things go right, use try else. You want to
>> cleanup no matter what happen, use try finally.
>>
>> There is no need of any Cleanup class, except for some technical
>> alternative concern.
>
> Have you considered that your argument applies to the "with" construct?
>
> You have probably not realized that.
>
> But let me force it on you: when would you use "with"?

When there is a specific context manager that removes the need for boilerplate.

> Check if that case is covered by your argument above.
>
> Now that you've been told about the "with" angle, don't you think it's a
> kind of weakness in your argument that it calls for removing "with" from
> the language?

No, it only argues that "with Cleanup():" is supernumerary.

Jean-Michel Pichavant

unread,
Mar 4, 2010, 11:32:10 AM3/4/10
to Alf P. Steinbach, pytho...@python.org
I am using python 2.5, so I know nothing about the with statement, and
it may possible my arguments apply to it, you could remove it from the
language, it wouldn't bother me at all.
I just don't see in what you've written (adding a class, with some
__entry__, __exit__ protocol, using a with statement) what cannot be
achieved with a try statement in its simpliest form.

Try except may be lame and noobish, but it works, is easy to read and
understood at first glance.
It looks like to me that 'with' statements are like decorators:
overrated. Sometimes people could write simple readable code, but yet
they're tempted by the geek side of programming: using complex
constructs when there's no need to. I myself cannot resist sometimes ;-)

JM

Alf P. Steinbach

unread,
Mar 4, 2010, 11:56:34 AM3/4/10
to
* Robert Kern:
> On 2010-03-03 18:49 PM, Alf P. Steinbach wrote:
>> * Robert Kern:
[snip]

>>> 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.
[snip middle of this paragraph]

> Why do you think that we would interpret those words
> to mean that you wanted the example you give just above?

There's an apparent discrepancy between your call for an example and your
subsequent (in the same paragraph) reference to the example given.

But as to why I assumed that that example, or a similar correct one, would be
implied, it's the only meaningful interpretation.

Adopting a meaningless interpretation when a meaningful exists is generally just
adversarial, but in this case I was, as you pointed out, extremely unclear, and
I'm sorry: I should have given such example up front. Will try to do so.


[snip]

Not to mention "with".

Some other poster made the same error recently in this thread; it is a common
fallacy in discussions about programming, to assume that since the same can be
expressed using lower level constructs, those are all that are required.

If adopted as true it ultimately means the removal of all control structures
above the level of "if" and "goto" (except Python doesn't have "goto").


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

Just a nit (I agree that a lambda can't do this, but as to what's required):
assigning None is sufficient for that[1].

However, note that the current language doesn't guarantee such cleanup, at least
as far as I know.

So while it's good practice to support it, to do everything to let it happen,
it's presumably bad practice to rely on it happening.


> Tracebacks will keep the namespace alive and all objects
> in it.

Thanks!, I hadn't thought of connecting that to general cleanup actions.

It limits the use of general "with" in the same way.


Cheers,

- Alf

Notes:
[1] An 'except' clause deletes variables, but since it has no knowledge of the
code it's placed in the only alternatives would be a presumably costly check of
prior existence, or letting it pollute the namespace.

Robert Kern

unread,
Mar 4, 2010, 12:00:32 PM3/4/10
to pytho...@python.org
On 2010-03-04 10:32 AM, Jean-Michel Pichavant wrote:
> I am using python 2.5, so I know nothing about the with statement,

You can try it out using "from __future__ import with_statement".

> and
> it may possible my arguments apply to it, you could remove it from the
> language, it wouldn't bother me at all.
> I just don't see in what you've written (adding a class, with some
> __entry__, __exit__ protocol, using a with statement) what cannot be
> achieved with a try statement in its simpliest form.
>
> Try except may be lame and noobish, but it works, is easy to read and
> understood at first glance.
> It looks like to me that 'with' statements are like decorators:
> overrated. Sometimes people could write simple readable code, but yet
> they're tempted by the geek side of programming: using complex
> constructs when there's no need to. I myself cannot resist sometimes ;-)

PEP 343 is a good introduction to the real uses of the with: statement.

http://www.python.org/dev/peps/pep-0343/

Basically, it allows you to package up your initialization and cleanup code into
objects, stick them in your library, unit test them thoroughly, etc. so you
don't have to repeat them everywhere and possibly get them wrong. It's DRY in
action.

Where Alf's Cleanup class goes wrong, in my opinion, is that it does not package
up any code to avoid repetition. You still repeat the same cleanup code
everywhere you use it, so it is no better than try: finally:. It is not a real
use case of the with: statement.

Alf P. Steinbach

unread,
Mar 4, 2010, 12:02:23 PM3/4/10
to
* Robert Kern:

> On 2010-03-04 09:48 AM, Alf P. Steinbach wrote:
>> * Jean-Michel Pichavant:
>>> Alf P. Steinbach wrote:
>>>>> 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.
>>>
>>> You want to execute some cleanup when things go wrong, use try except.
>>> You want to do it when things go right, use try else. You want to
>>> cleanup no matter what happen, use try finally.
>>>
>>> There is no need of any Cleanup class, except for some technical
>>> alternative concern.
>>
>> Have you considered that your argument applies to the "with" construct?
>>
>> You have probably not realized that.
>>
>> But let me force it on you: when would you use "with"?
>
> When there is a specific context manager that removes the need for
> boilerplate.

That's "cleanup no matter what happen".


>> Check if that case is covered by your argument above.
>>
>> Now that you've been told about the "with" angle, don't you think it's a
>> kind of weakness in your argument that it calls for removing "with" from
>> the language?
>
> No, it only argues that "with Cleanup():" is supernumerary.

I don't know what "supernumerary" means, but to the degree that the argument
says anything about a construct that is not 'finally', it says the same about
general "with".

So whatever you mean by supernumerary, you're saying that the argument implies
that "with" is supernumerary.

This is starting to look like some earlier discussions in this group, where even
basic logic is denied.


Cheers,

- Alf

Robert Kern

unread,
Mar 4, 2010, 12:20:20 PM3/4/10
to pytho...@python.org
On 2010-03-04 10:56 AM, Alf P. Steinbach wrote:
> * Robert Kern:
>> On 2010-03-03 18:49 PM, Alf P. Steinbach wrote:
>>> * Robert Kern:
> [snip]
>>>> 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.
> [snip middle of this paragraph]
>> Why do you think that we would interpret those words to mean that you
>> wanted the example you give just above?
>
> There's an apparent discrepancy between your call for an example and
> your subsequent (in the same paragraph) reference to the example given.
>
> But as to why I assumed that that example, or a similar correct one,
> would be implied, it's the only meaningful interpretation.
>
> Adopting a meaningless interpretation when a meaningful exists is
> generally just adversarial, but in this case I was, as you pointed out,
> extremely unclear, and I'm sorry: I should have given such example up
> front. Will try to do so.

Thank you. I appreciate it.

What I'm trying to explain is that the with: statement has a use even if Cleanup
doesn't. Arguing that Cleanup doesn't improve on try: finally: does not mean
that the with: statement doesn't improve on try: finally:.

>>>> 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.
>
> Just a nit (I agree that a lambda can't do this, but as to what's
> required): assigning None is sufficient for that[1].

Yes, but no callable is going to allow you to assign None to names in that
namespace, either. Not without sys._getframe() hackery, in any case.

> However, note that the current language doesn't guarantee such cleanup,
> at least as far as I know.
>
> So while it's good practice to support it, to do everything to let it
> happen, it's presumably bad practice to rely on it happening.
>
>
>> Tracebacks will keep the namespace alive and all objects in it.
>
> Thanks!, I hadn't thought of connecting that to general cleanup actions.
>
> It limits the use of general "with" in the same way.

Not really. It's easy to write context managers that do that. You put the
initialization code in the __enter__() method, assign whatever objects you want
to keep around through the with: clause as attributes on the manager, then
delete those attributes in the __exit__(). Or, you use the @contextmanager
decorator to turn a generator into a context manager, and you just assign to
local variables and del them in the finally: clause.

What you can't do is write a generic context manager where the initialization
happens inside the with: clause and the cleanup actions are registered
callables. That does not allow you to affect the namespace.

Robert Kern

unread,
Mar 4, 2010, 12:30:03 PM3/4/10
to pytho...@python.org

For the "# Do stuff" block, yes. For the initialization block, you can write a
context manager to do it either way, as necessary.

>>> Check if that case is covered by your argument above.
>>>
>>> Now that you've been told about the "with" angle, don't you think it's a
>>> kind of weakness in your argument that it calls for removing "with" from
>>> the language?
>>
>> No, it only argues that "with Cleanup():" is supernumerary.
>
> I don't know what "supernumerary" means,

http://www.merriam-webster.com/dictionary/supernumerary

> but to the degree that the
> argument says anything about a construct that is not 'finally', it says
> the same about general "with".

He's ignorant of the use cases of the with: statement, true. Given only your
example of the with: statement, it is hard to fault him for thinking that try:
finally: wouldn't suffice.

Michael Rudolf

unread,
Mar 4, 2010, 12:50:32 PM3/4/10
to
Am 04.03.2010 18:20, schrieb Robert Kern:
>
> What I'm trying to explain is that the with: statement has a use even if
> Cleanup doesn't. Arguing that Cleanup doesn't improve on try: finally:
> does not mean that the with: statement doesn't improve on try: finally:.

Yes, the with-statement rocks :)

I suggested such a thing a few days ago in another thread, where OP
wanted a "silent" keyword like:

silent:
do_stuff()

would be equivalent to:
try: do_stuff() except: pass

Of course catching *all* exceptions was a bad idea, so I came up with
the code below and now I actually like it and use it myself :)

---------snip---------
To your first question about a "silenced" keyword: you could emulate
this with context managers I guess.

Something like (untested, just a quick mockup how it could look):

class silenced:
def __init__(self, *silenced):
self.exceptions=tuple(silenced) #just to be explicit
def __enter__(self):
return self #dito
def __exit__(self, type, value, traceback):
for ex in self.exceptions:
if isinstance(value, ex):
return True #supresses exception


So:

with silenced(os.Error):
os.remove(somefile)

Would translate to:

try:
os.remove(somefile)
except os.Error:
pass

One nice thing about this approach would be that you can alias a set of
exceptions with this:

idontcareabouttheseerrors=silenced(TypeError, ValueError, PEBCAKError,
SyntaxError, EndOfWorldError, 1D10T_Error)

with idontcareabouttheseerrors:
do_stuff()

Regards,
Michael

Michael Rudolf

unread,
Mar 4, 2010, 12:55:18 PM3/4/10
to
Am 04.03.2010 17:32, schrieb Jean-Michel Pichavant:
> It looks like to me that 'with' statements are like decorators: overrated.

Oh no, you just insulted my favourite two python features, followed
immediately by generators, iterators and list comprehensions / generator
expressions :p

No, really: they *are* great ;D

Regards,
Michael

Jean-Michel Pichavant

unread,
Mar 4, 2010, 1:22:32 PM3/4/10
to Michael Rudolf, pytho...@python.org
They are great, and because of that greatness some of us, including me,
tend to use them where there's no point doing so.
I would never state that decorators are useless anyway, not in this
list, I value my life too much :-) (I'll give it a try in the perl list)

JM

Alf P. Steinbach

unread,
Mar 4, 2010, 1:37:23 PM3/4/10
to
* Robert Kern:

> On 2010-03-04 10:56 AM, Alf P. Steinbach wrote:
>> * Robert Kern:
>>> On 2010-03-03 18:49 PM, Alf P. Steinbach wrote:
[snippety]

>>>
>>>> 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.
>>
>> Not to mention "with".
>>
>> Some other poster made the same error recently in this thread; it is a
>> common fallacy in discussions about programming, to assume that since
>> the same can be expressed using lower level constructs, those are all
>> that are required.
>>
>> If adopted as true it ultimately means the removal of all control
>> structures above the level of "if" and "goto" (except Python doesn't
>> have "goto").
>
> What I'm trying to explain is that the with: statement has a use even if
> Cleanup doesn't. Arguing that Cleanup doesn't improve on try: finally:
> does not mean that the with: statement doesn't improve on try: finally:.

That's a different argument, essentially that you see no advantage for your
current coding patterns.

It's unconnected to the argument I responded to.

The argument that I responded to, that the possibility of expressing things at
the level of try:finally: means that a higher level construct is superfluous, is
still meaningless.

Sorry, it limits general 'with' in /exactly/ the same way.


> It's easy to write context managers that do that [delete objects from the namespace].

Sorry, no can do, as far as I know; your following example quoted below is an
example of /something else/.

And adding on top of irrelevancy, for the pure technical aspect it can be
accomplished in the same way using Cleanup (I provide an example below).

However, doing that would generally be worse than pointless since with good
coding practices the objects would become unreferenced anyway.


> You put
> the initialization code in the __enter__() method, assign whatever
> objects you want to keep around through the with: clause as attributes
> on the manager, then delete those attributes in the __exit__().

Analogously, if one were to do this thing, then it could be accomplished using a
Cleanup context manager as follows:

foo = lambda: None
foo.x = create_some_object()
at_cleanup.call( lambda o = foo: delattr( o, "x" ) )

... except that

1) for a once-only case this is less code :-)

2) it is a usage that I wouldn't recommend; instead I recommend adopting good
coding practices where object references aren't kept around.


> Or, you
> use the @contextmanager decorator to turn a generator into a context
> manager, and you just assign to local variables and del them in the
> finally: clause.

Uhm, you don't need a 'finally' clause when you define a context manager.

Additionally, you don't need to 'del' the local variables in @contextmanager
decorated generator.

The local variables cease to exist automatically.


> What you can't do is write a generic context manager where the
> initialization happens inside the with: clause and the cleanup actions
> are registered callables. That does not allow you to affect the namespace.

If you mean that you can't introduce direct local variables and have them
deleted by "registered callables" in a portable way, then right.

But I can't think of any example where that would be relevant; in particular
what matters for supporting on-destruction cleanup is whether you keep any
references or not, not whether you have a local variable of any given name.

And I think "with" is quite useful even with that restriction.


Cheers,

- Alf

Mike Kent

unread,
Mar 4, 2010, 3:48:30 PM3/4/10
to
On Mar 3, 10:56 am, "Alf P. Steinbach" <al...@start.no> 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
>
> by the way, that's one advantage:
>
> a "with Cleanup" is difficult to get wrong, while a "try" is easy to get wrong,
> as you did here
>
>    ---
>
> another general advantage is as for the 'with' statement generally

>
> >    original_dir = os.getcwd()
> >    try:
> >        os.chdir(somewhere)
> >        # Do other stuff
>
> also, the "do other stuff" can be a lot of code
>
> and also, with more than one action the try-else introduces a lot of nesting

>
> >    finally:
> >        os.chdir(original_dir)
> >        # Do other cleanup
>
> cheers & hth.,
>
> - alf

Wrong? In what way is my example wrong? It cleanly makes sure that
the current working directory is the same after the try/finally as it
was before it. Suboptimal, perhaps, in that the chdir in the finally
part is always executed, even if the chdir in the try part failed to
change the working directory.

That is a clear advantage to the code you presented, in that you have
the ability to register an 'undo' function only if the 'do' code
succeeded. Your code also avoids a problem with many nested try/
finally blocks. But for the simple chdir example you gave, I think
'wrong' isn't the word you were looking for regarding the try/finally
example I gave.

Anyway, I'll keep your code in mind the next time I want to avoid a
bunch of nested try/finally blocks.

Mike Kent

unread,
Mar 4, 2010, 4:12:01 PM3/4/10
to
On Mar 4, 12:30 pm, Robert Kern <robert.k...@gmail.com> wrote:

> He's ignorant of the use cases of the with: statement, true.

<humor> Ouch! Ignorant of the use cases of the with statement, am I?
Odd, I use it all the time. </humor>

> Given only your
> example of the with: statement, it is hard to fault him for thinking that try:
> finally: wouldn't suffice.

<humor> Damn me with faint praise, will you? </humor>

I'm kinda amazed at the drama my innocent request for the use case
elicited. From what I've gotten so far from this thread, for the
actual example Mr. Steinbach used, the only disadvantage to my counter-
example using try/finally is that the chdir in the finally part will
always be executed, even if the chdir in the try part did not
succeed. I concede that, and was aware of it when I wrote it. For
the simple example given, I did not consider it compelling. A more
complex example, that would have required multiple, nested try/finally
blocks, would show the advantages of Mr Steinbach's recipe more
clearly.

However, I fail to understand his response that I must have meant try/
else instead, as this, as Mr. Kern pointed out, is invalid syntax.
Perhaps Mr. Steinbach would like to give an example?

Mike Kent

unread,
Mar 4, 2010, 4:19:44 PM3/4/10
to
On Mar 3, 12:00 pm, Robert Kern <robert.k...@gmail.com> wrote:

> On 2010-03-03 09:39 AM, Mike Kent wrote:
>
> > What's the compelling use case for this vs. a simple try/finally?
>
> >     original_dir = os.getcwd()
> >     try:
> >         os.chdir(somewhere)
> >         # Do other stuff
> >     finally:
> >         os.chdir(original_dir)
> >         # Do other cleanup
>
> A custom-written context manager looks nicer and can be more readable.
>
> from contextlib import contextmanager
> import os
>
> @contextmanager
> def pushd(path):
>      original_dir = os.getcwd()
>      os.chdir(path)
>      try:
>          yield
>      finally:
>          os.chdir(original_dir)
>
> with pushd(somewhere):
>      ...

Robert, I like the way you think. That's a perfect name for that
context manager! However, you can clear one thing up for me... isn't
the inner try/finally superfluous? My understanding was that there
was an implicit try/finally already done which will insure that
everything after the yield statement was always executed.

Alf P. Steinbach

unread,
Mar 4, 2010, 5:27:03 PM3/4/10
to
* Mike Kent:

> On Mar 4, 12:30 pm, Robert Kern <robert.k...@gmail.com> wrote:
>
>> He's ignorant of the use cases of the with: statement, true.
>
> <humor> Ouch! Ignorant of the use cases of the with statement, am I?
> Odd, I use it all the time. </humor>
>
>> Given only your
>> example of the with: statement, it is hard to fault him for thinking that try:
>> finally: wouldn't suffice.
>
> <humor> Damn me with faint praise, will you? </humor>
>
> I'm kinda amazed at the drama my innocent request for the use case
> elicited. From what I've gotten so far from this thread, for the
> actual example Mr. Steinbach used, the only disadvantage to my counter-
> example using try/finally is that the chdir in the finally part will
> always be executed, even if the chdir in the try part did not
> succeed. I concede that, and was aware of it when I wrote it. For
> the simple example given, I did not consider it compelling.

Uhm, well.

My example was:

with Cleanup as at_cleanup:
# blah blah
chdir( somewhere )
at_cleanup.call( lambda: chdir( original_dir ) )
# blah blah

It was not intended to compel, rather just to illustrate usage. :-)

And you asked about comparing that with ...

original_dir = os.getcwd()
try:
os.chdir(somewhere)
# Do other stuff
finally:
os.chdir(original_dir)
# Do other cleanup

.. which does something different, namely, always executing the
os.chdir(original_dir) or more generally the action-specific cleanup.

The action-specific cleanup might be much more costly than a chdir, and/or, in
the case where the action failed, incorrect.

In the same number of lines and with fewer keystrokes you could have written
code that was equivalent to the code I posted and that you wanted to compare
with, e.g. ...

original_dir = os.getcwd()
os.chdir(somewhere)
try:


# Do other stuff
finally:
os.chdir(original_dir)
# Do other cleanup

... so given how easy it is to write such an equivalent code snippet, I assumed
that the different behavior was /not/ intended, that instead, you'd attempted to
write equivalent code but made a slight error in the translation to lower level
construct -- but impossible to say exactly what.

Now you write that you were "aware of [the different behavior] when I wrote it",
and that just baffles me: why not then, as a basis of sound comparision, write
the equivalent code shown above, the same number of lines as what you wrote?


> A more
> complex example, that would have required multiple, nested try/finally
> blocks, would show the advantages of Mr Steinbach's recipe more
> clearly.
>
> However, I fail to understand his response that I must have meant try/
> else instead, as this, as Mr. Kern pointed out, is invalid syntax.
> Perhaps Mr. Steinbach would like to give an example?

OK.

Assuming that you wanted the chdir to be within a try block (which it was in
your code), then to get code equivalent to my code, for the purpose of a
comparision of codes that do the same, you'd have to write something like ...

original_dir = os.getcwd()
try:
os.chdir(somewhere)

except Whatever:
# E.g. log it.
raise
else:
try:


# Do other stuff
finally:
os.chdir(original_dir)
# Do other cleanup

... which would be a more general case.

I've also given this example in response to Robert earlier in the thread.
Although I haven't tried it I believe it's syntactically valid. If not, then the
relevant typo should just be fixed. :-)

I have no idea which construct Robert thought was syntactically invalid. I think
that if he's written that, then it must have been something he thought of.

Robert Kern

unread,
Mar 4, 2010, 2:37:29 PM3/4/10
to pytho...@python.org

I am attacking your premise that the "with Cleanup():" construct is higher level
than try: finally:. It isn't. It provides the same level of abstraction as try:
finally:.

This is distinct from the accepted uses of the with: statement which *are*
higher level than try: finally: and which do confer practical benefits over
using try: finally: despite being syntactical sugar for try: finally:.

Okay, so what do you mean by 'the use of general "with"'? I'm talking about
writing a context manager or using the @contextmanager decorator to do some
initialization and then later cleaning up that initialization. That cleaning up
may entail deleting an object. You are correct that the context manager can't
affect the namespace of the with: clause, but that's not the initialization that
it would need to clean up.

Yes, you can write code with a with: statement where you try to clean up stuff
that happened inside of the clause (you did), but that's not how the with:
statement was ever intended to be used nor is it good practice to do so because
of that limitation. Context managers are designed to initialize specific things,
then clean them up. I thought you were talking about the uses of the with:
statement as described in PEP-343, not every possible misuse of the with: statement.

> And adding on top of irrelevancy, for the pure technical aspect it can
> be accomplished in the same way using Cleanup (I provide an example below).
>
> However, doing that would generally be worse than pointless since with
> good coding practices the objects would become unreferenced anyway.
>
>
>> You put the initialization code in the __enter__() method, assign
>> whatever objects you want to keep around through the with: clause as
>> attributes on the manager, then delete those attributes in the
>> __exit__().
>
> Analogously, if one were to do this thing, then it could be accomplished
> using a Cleanup context manager as follows:
>
> foo = lambda: None
> foo.x = create_some_object()
> at_cleanup.call( lambda o = foo: delattr( o, "x" ) )
>
> ... except that
>
> 1) for a once-only case this is less code :-)

Not compared to a try: finally:, it isn't.

> 2) it is a usage that I wouldn't recommend; instead I recommend adopting
> good
> coding practices where object references aren't kept around.

Many of the use cases of the with: statement involve creating an object (like a
lock or a transaction object), keeping it around for the duration of the "# Do
stuff" block, and then finalizing it.

>> Or, you use the @contextmanager decorator to turn a generator into a
>> context manager, and you just assign to local variables and del them
>> in the finally: clause.
>
> Uhm, you don't need a 'finally' clause when you define a context manager.

When you use the @contextmanager decorator, you almost always do. See the
Examples section of PEP 343:

http://www.python.org/dev/peps/pep-0343/

> Additionally, you don't need to 'del' the local variables in
> @contextmanager decorated generator.
>
> The local variables cease to exist automatically.

True.

>> What you can't do is write a generic context manager where the
>> initialization happens inside the with: clause and the cleanup actions
>> are registered callables. That does not allow you to affect the
>> namespace.
>
> If you mean that you can't introduce direct local variables and have
> them deleted by "registered callables" in a portable way, then right.
>
> But I can't think of any example where that would be relevant; in
> particular what matters for supporting on-destruction cleanup is whether
> you keep any references or not, not whether you have a local variable of
> any given name.

Well, local variables keep references to objects. Variable assignment followed
by deletion is a very readable way to keep an object around for a while then
remove it later. If you have to go through less readable contortions to keep the
object around when it needs to be and clean it up later, then that is a mark
against your approach.

Alf P. Steinbach

unread,
Mar 4, 2010, 6:52:04 PM3/4/10
to

I'm not the one talking about removing variables or that "it's easy to write
context managers that do that".

You are the one talking about that.

So I have really not much to add.

It seems that you're now agreeing with me that former is not good practice and
that the latter is impossible to do portably, but you now argue against your
earlier stand as if that was something that I had put forward.

It's a bit confusing when you argue against your own statements.


>> And adding on top of irrelevancy, for the pure technical aspect it can
>> be accomplished in the same way using Cleanup (I provide an example
>> below).
>>
>> However, doing that would generally be worse than pointless since with
>> good coding practices the objects would become unreferenced anyway.
>>
>>
>>> You put the initialization code in the __enter__() method, assign
>>> whatever objects you want to keep around through the with: clause as
>>> attributes on the manager, then delete those attributes in the
>>> __exit__().
>>
>> Analogously, if one were to do this thing, then it could be accomplished
>> using a Cleanup context manager as follows:
>>
>> foo = lambda: None
>> foo.x = create_some_object()
>> at_cleanup.call( lambda o = foo: delattr( o, "x" ) )
>>
>> ... except that
>>
>> 1) for a once-only case this is less code :-)
>
> Not compared to a try: finally:, it isn't.

Again, this context shifting is bewildering. As you can see, quoted above, you
were talking about a situation where you would have defined a context manager,
presumably because a 'try' would not in your opinion be simpler for whatever it
was that you had in mind. But you are responding to the code I offered as if it
was an alternative to something where you would find a 'try' to be simplest.

Sorry, as with the places noted above, I can't understand what you're trying to
say here. I don't recommend coding practices where you keep object references
around, and you have twice quoted that above. I don't have any clue what
"contortions" you are talking about, it must be something that you imagine.


Cheers,

- Alf (three times baffled)

Steve Holden

unread,
Mar 4, 2010, 2:49:27 PM3/4/10
to pytho...@python.org
Alf P. Steinbach wrote:
> * Robert Kern:
[...]

>> No, it only argues that "with Cleanup():" is supernumerary.
>
> I don't know what "supernumerary" means, but to the degree that the
> argument says anything about a construct that is not 'finally', it says
> the same about general "with".
>
So rather than look up the meaning if a word you aren't familiar with
you will argue against its use in generic style?

> So whatever you mean by supernumerary, you're saying that the argument
> implies that "with" is supernumerary.
>
> This is starting to look like some earlier discussions in this group,
> where even basic logic is denied.
>

Why not just stick to the facts and forget about the earlier discussions?

regards
Steve
--
Steve Holden +1 571 484 6266 +1 800 494 3119
PyCon is coming! Atlanta, Feb 2010 http://us.pycon.org/
Holden Web LLC http://www.holdenweb.com/
UPCOMING EVENTS: http://holdenweb.eventbrite.com/

Robert Kern

unread,
Mar 4, 2010, 8:04:30 PM3/4/10
to pytho...@python.org

No, the try: finally: is not implicit. See the source for
contextlib.GeneratorContextManager. When __exit__() gets an exception from the
with: block, it will push it into the generator using its .throw() method. This
raises the exception inside the generator at the yield statement.

Alf P. Steinbach

unread,
Mar 4, 2010, 8:59:31 PM3/4/10
to
* Steve Holden:

> Alf P. Steinbach wrote:
>> * Robert Kern:
> [...]
>>> No, it only argues that "with Cleanup():" is supernumerary.
>> I don't know what "supernumerary" means, but to the degree that the
>> argument says anything about a construct that is not 'finally', it says
>> the same about general "with".
>>
> So rather than look up the meaning if a word you aren't familiar with
> you will argue against its use in generic style?

I haven't argued against the use of the word. I haven't done so in generic
style, and I haven't argued against generic style use of the word, whatever it
is you're trying to say. And I see that you're out trolling again, Steve Holden,
implying all sorts of things that are untrue, as is evidently still your style.


>> So whatever you mean by supernumerary, you're saying that the argument
>> implies that "with" is supernumerary.
>>
>> This is starting to look like some earlier discussions in this group,
>> where even basic logic is denied.
>>
> Why not just stick to the facts and forget about the earlier discussions?

For yet another example, here you are implying that I'm not sticking to facts,
which, again, is untrue, a technique that you should be ashamed of, Steve Holden.

And since you're now injecting some Steve Holden'sk noise into this debate,
chances are that in your points- and win/lose fixation you think I have scored a
point immediately upthread, something which you think needs drowning in noise.


Cheers,

- Alf

Steve Howell

unread,
Mar 4, 2010, 11:37:50 PM3/4/10
to
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)

Robert Kern

unread,
Mar 4, 2010, 10:42:32 PM3/4/10
to pytho...@python.org
On 2010-03-04 16:27 , Alf P. Steinbach wrote:
> * Mike Kent:

I was just trying to interpret what you meant by "Changing 'finally' to 'else'
could make it equivalent." As far as I can tell, that has only one possible
interpretation going by the plain meaning of the words, and it isn't yours.
Since you always seem to refer to "try/else" as if it were an independent
construct and not part of "try: except: else:" and no one else introduced
except: clause, I must reiterate that your communications have been fabulously
misleading.

Robert Kern

unread,
Mar 4, 2010, 10:35:13 PM3/4/10
to pytho...@python.org
On 2010-03-04 15:12 , Mike Kent wrote:
> On Mar 4, 12:30 pm, Robert Kern<robert.k...@gmail.com> wrote:
>
>> He's ignorant of the use cases of the with: statement, true.
>
> <humor> Ouch! Ignorant of the use cases of the with statement, am I?
> Odd, I use it all the time.</humor>

No, I was referring to Jean-Michel, who was not familiar with the with: statement.

>> Given only your
>> example of the with: statement, it is hard to fault him for thinking that try:
>> finally: wouldn't suffice.
>
> <humor> Damn me with faint praise, will you?</humor>

Also talking about Jean-Michel. :-)

Robert Kern

unread,
Mar 4, 2010, 11:01:44 PM3/4/10
to pytho...@python.org

No, I'm still saying that sometimes you do need to remove variables that you
initialized in the initialization section. Writing a purpose-built context
manager which encapsulates the initialization and finalization allows you to do
this easily. Putting the initialization section inside the with: clause, as you
do, and requiring cleanup code to be put into callables makes this hard.

> It's a bit confusing when you argue against your own statements.
>
>>> And adding on top of irrelevancy, for the pure technical aspect it can
>>> be accomplished in the same way using Cleanup (I provide an example
>>> below).
>>>
>>> However, doing that would generally be worse than pointless since with
>>> good coding practices the objects would become unreferenced anyway.
>>>
>>>
>>>> You put the initialization code in the __enter__() method, assign
>>>> whatever objects you want to keep around through the with: clause as
>>>> attributes on the manager, then delete those attributes in the
>>>> __exit__().
>>>
>>> Analogously, if one were to do this thing, then it could be accomplished
>>> using a Cleanup context manager as follows:
>>>
>>> foo = lambda: None
>>> foo.x = create_some_object()
>>> at_cleanup.call( lambda o = foo: delattr( o, "x" ) )
>>>
>>> ... except that
>>>
>>> 1) for a once-only case this is less code :-)
>>
>> Not compared to a try: finally:, it isn't.
>
> Again, this context shifting is bewildering. As you can see, quoted
> above, you were talking about a situation where you would have defined a
> context manager, presumably because a 'try' would not in your opinion be
> simpler for whatever it was that you had in mind. But you are responding
> to the code I offered as if it was an alternative to something where you
> would find a 'try' to be simplest.

I have consistently put forward that for once-only cases, try: finally: is a
preferable construct to "with Cleanup():". I also put forward that for
repetitive cases, a purpose-built context manager is preferable. I note that for
both try: finally: and a purpose-built context manager, modifying the namespace
is easy to do while it is difficult and less readable to do with "with
Cleanup():". Since you were claiming that the "generic with:" would have the
same problem as "with Cleanup():" I was trying to explain for why all of the
intended use cases of the with: statement (the purpose-built context managers),
there is no problem. Capisce?

Then how do you clean up locks and transaction objects and similar things if you
don't keep them around during the code suite? Creating an object, keeping it
around while executing a specific chunk of code, and finalizing it safely is a
prime use case for these kinds of constructs.

> and you have twice quoted that above. I don't
> have any clue what "contortions" you are talking about, it must be
> something that you imagine.

These are the contortions:

foo = lambda: None
foo.x = create_some_object()
at_cleanup.call( lambda o = foo: delattr( o, "x" ) )

--

Jean-Michel Pichavant

unread,
Mar 5, 2010, 7:20:04 AM3/5/10
to Robert Kern, pytho...@python.org
Robert Kern wrote:
> On 2010-03-04 15:12 , Mike Kent wrote:
>> On Mar 4, 12:30 pm, Robert Kern<robert.k...@gmail.com> wrote:
>>
>>> He's ignorant of the use cases of the with: statement, true.
>>
>> <humor> Ouch! Ignorant of the use cases of the with statement, am I?
>> Odd, I use it all the time.</humor>
>
> No, I was referring to Jean-Michel, who was not familiar with the
> with: statement.
>
>>> Given only your
>>> example of the with: statement, it is hard to fault him for thinking
>>> that try:
>>> finally: wouldn't suffice.
>>
>> <humor> Damn me with faint praise, will you?</humor>
>
> Also talking about Jean-Michel. :-)
>
I confirm, I am the ignoramus (what a strange word) in this story :-)

JM

Alf P. Steinbach

unread,
Mar 5, 2010, 8:06:55 AM3/5/10
to

Where you need a scope, create a scope.

That is, put the logic in a routine.

However, most objects aren't of the sort the requiring to become unreferenced.
Those that require cleanup can be cleaned up and left as zombies, like calling
'close' on a file. And so in the general case what you're discussing, how to get
rid of object references, is a non-issue -- irrelevant.


>> and you have twice quoted that above. I don't
>> have any clue what "contortions" you are talking about, it must be
>> something that you imagine.
>
> These are the contortions:
>
> foo = lambda: None
> foo.x = create_some_object()
> at_cleanup.call( lambda o = foo: delattr( o, "x" ) )

That's code that demonstrates something very different, in response to your
description of doing this.

Since I half suspected that it could be taken out of context I followed that
immediately with

<quote>


2) it is a usage that I wouldn't recommend; instead I recommend adopting
good coding practices where object references aren't kept around.

</quote>

I'm sorry but I don't know how to make that more clear.

Alf P. Steinbach

unread,
Mar 5, 2010, 8:09:08 AM3/5/10
to
* Robert Kern:

Oh yes, in the article where I gave the example of that, shown above.

Hey, discussing previous discussion is silly.


Cheers,

- ALf

Mike Kent

unread,
Mar 5, 2010, 11:29:59 AM3/5/10
to
On Mar 4, 8:04 pm, Robert Kern <robert.k...@gmail.com> wrote:

> No, the try: finally: is not implicit. See the source for
> contextlib.GeneratorContextManager. When __exit__() gets an exception from the
> with: block, it will push it into the generator using its .throw() method. This
> raises the exception inside the generator at the yield statement.

Wow, I just learned something new. My understanding of context
managers was that the __exit__ method was guaranteed to be executed
regardless of how the context was left. I have often written my own
context manager classes, giving them the __enter__ and __exit__
methods. I had mistakenly assumed that the @contextmanager decorator
turned a generator function into a context manager with the same
behavior as the equivalent context manager class. Now I learn that,
no, in order to have the 'undo' code executed in the presence of an
exception, you must write your own try/finally block in the generator
function.

This raises the question in my mind: What's the use case for using
@contextmanager rather than wrapping your code in a context manager
class that defines __enter__ and __exit__, if you still have to
manager your own try/finally block?

Steve Howell

unread,
Mar 5, 2010, 11:38:27 AM3/5/10
to

Unless I am misunderstanding the question, the use case is that you
still only have to write the context manager once, and you might get
multiple uses out of it where the with-enclosed code blocks work at a
higher level of abstraction.

I actually don't use @contextmanager yet, mainly because I did not
know it existed until recently, but also because I find the __enter__/
__exit__ paradigm straightforward enough to just hand code them that
way.

Steve Howell

unread,
Mar 5, 2010, 11:49:41 AM3/5/10
to

See also:

http://docs.python.org/library/contextlib.html

The closing() helper can be used to automatically call thing.close()
even after an exception.

If you do not use the closing() helper and take on the responsibility
yourself of doing try/finally within your generator, I think you still
gain some overall simplicity:

1) You don't need to do try/finally in your with blocks, of course.
2) The generator itself probably reads more straightforwardly then a
hand-coded class with __enter__ and __exit__.

For point #2, I think there are probably different aesthetics.
Generators are more concise, but they are also a bit mind-bending.

Robert Kern

unread,
Mar 5, 2010, 12:00:22 PM3/5/10
to pytho...@python.org
On 2010-03-05 10:29 AM, Mike Kent wrote:
> On Mar 4, 8:04 pm, Robert Kern<robert.k...@gmail.com> wrote:
>
>> No, the try: finally: is not implicit. See the source for
>> contextlib.GeneratorContextManager. When __exit__() gets an exception from the
>> with: block, it will push it into the generator using its .throw() method. This
>> raises the exception inside the generator at the yield statement.
>
> Wow, I just learned something new. My understanding of context
> managers was that the __exit__ method was guaranteed to be executed
> regardless of how the context was left.

It is. @contextmanager turns a specially-written generator into a context
manager with an __exit__ that does different things depending on whether or not
and exception was raised. By pushing the exception into the generator, it lets
the author decide what to do. It may catch a subset of exceptions, or no
exceptions, or use a finally:. They all have use cases although finally: is the
usual one.

> I have often written my own
> context manager classes, giving them the __enter__ and __exit__
> methods. I had mistakenly assumed that the @contextmanager decorator
> turned a generator function into a context manager with the same
> behavior as the equivalent context manager class.

Basically, it does. __exit__() is given the exception information. When you
write such a class, you can decide what to do with the exception. You can
silence it, immediately reraise it, conditionally reraise it, log it and then
reraise it, etc. Pushing the exception into the generator keeps this flexibility
and the equivalence. If it removed that choice, then it would not be equivalent.

> Now I learn that,
> no, in order to have the 'undo' code executed in the presence of an
> exception, you must write your own try/finally block in the generator
> function.
>
> This raises the question in my mind: What's the use case for using
> @contextmanager rather than wrapping your code in a context manager
> class that defines __enter__ and __exit__, if you still have to
> manager your own try/finally block?

The @contextmanager generator implementations are often shorter and easier to
read, in my opinion, partly because they use the try: finally: syntax that most
of us are very familiar with. I have to think less when I read it because it
looks so similar to the equivalent code that you would normally write.

The point of context managers isn't to remove the use of try: finally: entirely,
but to implement it once so that it can be reused cleanly. You only have to
write the one try: finally: in the generator and reuse it simply with the with:
statement in many places.

Alf P. Steinbach

unread,
Mar 5, 2010, 2:13:23 PM3/5/10
to
* Mike Kent:

Robert Kern and Steve Howell have already given given good answers.

As it happened this was news to me also, because I'm not that well-versed in
Python and it seems contrary to the purpose of providing a simpler way to write
a simple init-cleanup wrapper.

But additionally, if you want that, then you can define it, e.g.


<code>
# Py3

def simplecleanup( generator_func ):
class SimpleCleanup:
def __init__( self, *args, **kwargs ):
self.generator = generator_func( *args, **kwargs )

def __enter__( self ):
self.generator.send( None )
return self

def __exit__( self, x_type, x_obj, x_traceback ):
try:
self.generator.send( x_obj ) # x_obj is None if no exception
except StopIteration:
pass # Expected

return SimpleCleanup


@simplecleanup
def hello_goodbye( name ):
print( "Hello, {}!".format( name ) )
yield
print( "Goodbye {}!".format( name ) )


try:
with hello_goodbye( "Mary" ):
print( "Talk talk talk..." )
raise RuntimeError( "Offense" )
except:
pass
print()


@simplecleanup
def sensitive_hello_goodbye( name ):
print( "Hello, {}!".format( name ) )
x = yield
if x is not None:
print( "Uh oh, {}!".format( x ) )
print( "Good day {}!".format( name ) )
else:
print( "C u, {}!".format( name ) )


try:
with sensitive_hello_goodbye( "Jane" ):
print( "Talk talk talk..." )
raise RuntimeError( "Offense" )
except:
pass
</code>


Cheers,

- Alf

Alf P. Steinbach

unread,
Mar 5, 2010, 2:34:00 PM3/5/10
to
* Steve Howell:

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$p1i$1...@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

Alf P. Steinbach

unread,
Mar 5, 2010, 9:32:48 PM3/5/10
to
* Robert Kern:

> On 2010-03-03 09:39 AM, Mike Kent wrote:
>> What's the compelling use case for this vs. a simple try/finally?
>>
>> original_dir = os.getcwd()
>> try:
>> os.chdir(somewhere)
>> # Do other stuff
>> finally:
>> os.chdir(original_dir)
>> # Do other cleanup
>
> A custom-written context manager looks nicer and can be more readable.
>
> from contextlib import contextmanager
> import os
>
> @contextmanager
> def pushd(path):
> original_dir = os.getcwd()
> os.chdir(path)
> try:
> yield
> finally:
> os.chdir(original_dir)
>
>
> with pushd(somewhere):
> ...
>
>
> I don't think a general purpose ScopeGuard context manager has any such
> benefits over the try: finally:, though.

class pushd( Cleanup ):
def __init__( self, path ):
original_dir = os.getcwd()
os.chdir( path )
self._actions.append( lambda: os.chdir( original_dir ) )


Disclaimer: haven't tested this, but it just occurred to me that for such small
init/cleanup wrappers the Cleanup class provides a nice alternative to
@contextmanager, as above: fewer lines, and perhaps even more clear code. :-)


Cheers,

- Alf

Gabriel Genellina

unread,
Mar 7, 2010, 9:00:41 PM3/7/10
to pytho...@python.org
En Thu, 04 Mar 2010 20:52:04 -0300, Alf P. Steinbach <al...@start.no>
escribiᅵ:

> Sorry, as with the places noted above, I can't understand what you're
> trying to say here.

Regarding your posts, neither can I. All the time. Sorry, deciphering your
posts would force me to spend much more time than available and I can't
afford that - so I'm blocking your messages from now on.

--
Gabriel Genellina

Alf P. Steinbach

unread,
Mar 8, 2010, 2:38:55 AM3/8/10
to
* Gabriel Genellina:


You could just ask if there was anything you didn't understand.

Even with as little you quote readers can see my approach to that problem: asking.

But your response, both the out of context quoting and your comment, seems
solely designed to convey a negative impression instead of addressing any
technical issue.


Cheers,

- Alf

Steve Holden

unread,
Mar 8, 2010, 7:28:06 AM3/8/10
to pytho...@python.org
This isn't an isolated case, Alf. Physician, heal thyself.

sste...@gmail.com

unread,
Mar 8, 2010, 9:03:21 AM3/8/10
to Steve Holden, pytho...@python.org
On Mar 8, 2010, at 7:28 AM, Steve Holden wrote:

>>
>> But your response, both the out of context quoting and your comment,
>> seems solely designed to convey a negative impression instead of
>> addressing any technical issue.
>>

> This isn't an isolated case, Alf. Physician, heal thyself.

As far as I can tell, this happens on every thread Alf is involved in at one point or another. I'm sure it's "everyone else."

S

0 new messages