[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