On Apr 16, 2015, at 09:25, Steven D'Aprano email@example.com wrote:
On Thu, Apr 16, 2015 at 06:25:37AM -0700, Andrew Barnert wrote: On Apr 16, 2015, at 06:09, Steven D'Aprano firstname.lastname@example.org 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:
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()
That's clearly different. There is nothing inherent to function call syntax that relates to finally; there is something inherent to a with statement that does. You might just as well argue that there's nothing special about for blocks that go with iteration because any function definition may have a call to iter inside.
On top of that, inside a function is an entirely different scope; what happens inside a function is in many senses hidden or isolated from syntax. For example, compare this:
for i in range(10): if i == 3: break else: print('broke')
for i in range(10): if i == 3: eggs() else: print('broke')
If you told me there was a break inside eggs, it wouldn't make any difference. A call to eggs is still not equivalent to a break in this scope, just as a call to spam is still not equivalent to a finally in this scope.
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".
No more than I'd decide that eggs is special.
You could, of course, argue that spam is a different case from eggs because the one exception to scope locality is exceptions, and with statements, unlike break, are fundamentally about exceptions... But that requires assuming exactly the point you're trying to disprove.
Again, I agree with you that this proposal is probably a bad idea. But this argument against it doesn't work.