On Wed, Apr 15, 2015 at 11:39:37PM -0700, Kale Kundert wrote:
I'd like to propose a little bit of syntactic sugar: allowing with-blocks to be followed by except- and finally-blocks just like try-blocks. For example:
with open('spam.txt') as file: print(file.read()) except IOError: print('No spam here...') finally: print('done.')
This has two obvious, and slightly different, interpretations:
with open('spam.txt') as file: try: print(file.read()) except IOError: print('No spam here...') finally: print('done.')
try: with open('spam.txt') as file: print(file.read()) except IOError: print('No spam here...') finally: print('done.')
I'm not even going to ask you which of the two you intended, because whichever one you pick, half the time people will want the other. And of those times, half the time they will end up using this syntactic sugar anyway, and then be disturbed when it doesn't quite work the way they expected.
To put it another way, I expect this to be an oh-so-subtle bug magnet.
This proposed syntax is semantically equivalent to wrapping a with-block within a try-block like so:
try: with open('spam.txt') as file: print(file.read()) finally: print('done.') except IOError: print('No spam here...')
You can't describe "with...finally" in terms of "with...finally", it gives a RecursionError :-)
I assume this is an editing mistake, and you intended the finally clause to follow the except clause.
I see two advantages to the proposed syntax. First and most obviously, it saves an extra line and an extra indentation level. One line may not be a big deal, but one indentation level can really affect readability.
It certainly can affect readability, but not in the way you mean. The loss of that indentation level *hurts* readability, it doesn't help it, because it obscures the structure of the code.
Either the try block encloses the with, or the with encloses the try. They are not part of the same structure in the way that "if...elif... else" or "for...else" are.
[Aside: in the second case, the for-loop, "else" is not the best choice of keyword. The semantics are more like "then", but we are probably stuck with it now.]
Given either version:
with spam: try: X except: ...
try: with spam: X except: ...
the fact that X is indented two levels is a good thing, not a bad thing. It makes the structure of the code immediately visible, and there is absolutely no question of whether the with is enclosing the try or the try is enclosing the with.
Existing Python statements which introduce blocks strictly keep to the rule that things which are semantically at different levels are indented differently. (The exception is the single line form of some statements, e.g. "if condition: pass" on a single line.) We never write things like:
if condition: for x in sequence: do_this()
try: while flag: do_this() except Error: handle_error()
def func(arg): with func(arg) as spam: return spam.thing()
even in the cases where they wouldn't be syntactically ambiguous. Such things are strictly disallowed, and that is good.
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.
More often than not, if you're using a with-block, you're expecting that something in that block could throw an exception.
"More often than not"? Not according to your own investigation, where you found that only two percent of the time with blocks are associated with try blocks. That means that in the overwhelming majority of cases, nearly 98% of the time, we are NOT catching exceptions in the with-block.
That means that *at best* this syntactic sugar will be of little value, and at worst it will be actively harmful. I think it will be actively harmful, and give it a strong -1 vote.