[Python-ideas] with-except-finally blocks

Steven D'Aprano steve at pearwood.info
Thu Apr 16 18:25:33 CEST 2015


On Thu, Apr 16, 2015 at 06:25:37AM -0700, Andrew Barnert wrote:
> On Apr 16, 2015, at 06:09, Steven D'Aprano <steve at pearwood.info> wrote:
> 
> >> Second and more
> >> conceptually, it makes sense to think about exception handling in the context of
> >> a with-block.
> > 
> > No more than any other block of code. There is nothing special about 
> > with blocks that go with exception handling, any more than (say) 
> > indexing into a list, processing a dict, or importing a module.
> 
> I think the key insight behind this is that a context manager is in 
> some sense equivalent to a try/finally--and can in fact be implemented 
> that way using @contextmanager. That isn't true for any of the other 
> cases.

You are correct that with... statements are, in a sense, equivalent to 
try...finally. That's hardly a secret or an insight -- I too have read 
the docs and the PEP :-) But I think it is irrelevant to this question. 
Anything we write might contain an internal try...finally. Consider:

spam()

It is, I hope, obvious that there is nothing special about spam that 
relates it to try...finally more than any other chunk of code. But what 
if I told you that *inside* spam there was a try...finally clause?

def spam():
    try: eggs()
    finally: cheese()

I hope that you wouldn't change your mind and decide that spam was 
special and we should try to create syntactic sugar:

spam()
finally:
    tomato()

because "spam is in some sense equivalent to a try/finally".

Now substitute with statements for spam. Yes, "with..." is fundamentally 
linked to a finally block. It's part of the definition of the with 
statement. But that finally block is controlled by the context manager, 
not us -- as users of the with statement, we don't get to control what 
happens in the finally clause unless we write our own try...finally 
block. Exactly the same as spam and its internal finally clause.

With the proposed syntax:

with something():
    block
finally:
    print("Done")

there are conceptually two independent finally blocks. There is the 
finally block hidden inside the context manager's __exit__ method, which 
is part of the with statement. As a consumer of the CM, we don't have 
any control over that. And there is our finally block, which prints 
"Done", which is independent of the context manager and under our 
control.

That raises another interesting question. Which finally clause will 
trigger first? With current syntax, the block structure is explicit and 
obvious:

with something():
    try:
        block
    finally:
        print("Done")

"Done" is printed before the __exit__ method is called, because that
finally clause is part of the with block. Whereas:

try:
    with something():
        block
finally:
    print("Done")

in this case, the __exit__ method is called first. The block structure 
makes that so obvious I don't even need to test it to be sure that it 
must be the case. (Famous last words!)



-- 
Steven


More information about the Python-ideas mailing list