[Python-ideas] Potential PEP: with/except

Steven D'Aprano steve at pearwood.info
Tue Jan 22 20:28:26 EST 2019


I've been thinking more about this proposal, and realised why I've been 
feeling a slight sense of disquiet about it. I think it encourages an 
anti-pattern of catching too much. (Or at least a code smell.)

Although we're all guilty of violating this principle from time to time, 
in general we ought to surround the minimum amount of code with a 
try...except that we need. Ideally (but rarely possible in practice) we 
want to surround a single operation which might raise at a time.

Otherwise, we risk this sort of failure:

    try:
        n = len(sequence)
        result = process(n)
    except TypeError:
        # we implicitly assume that the ONLY source of
        # TypeError is calling len(sequence)
        handle_iterator(sequence)

But what if process(n) itself raises TypeError? Perhaps because it takes 
two mandatory arguments, not one, and we've just hidden a bug in our 
code.

Now obviously this specific example is just a toy, but the principle 
applies. When I see code like:

    try:
        with spam(arg) as x:
            block
    except SomeException:
        # implicitly assume that spam(arg) is the only
        # thing which can fail
        handle_failure_in_spam()

what I see is a try block which may be too greedy, possibly hiding bugs 
in the code.

So what we probably *actually* want is:

    try:
        tmp = spam(arg)
    except SomeException:
        handle_failure_in_spam()
    with tmp as x:
        block

but who can be bothered writing that? At least until they've been bitten 
by the failure to do so.

Given this:

    with spam(arg) as x:
        block
    except:
        ...

what is the scope of this with...except clause? 

* just the call to spam(arg)

- the call to spam(arg) and the call to x.__enter__

- spam(arg), x.__enter__ and the entire with block

- just the call to x.__enter__

- just the block

Visually, the beauty of the try...except syntax is that there is nothing 
else happening on the try line. The try statement is purely a delimiter, 
and it is *only* the indented block below it which is guarded.

But the "with..." line in this proposal acts as both delimiter and code, 
and so it is ambiguous whether we want the delimiter to come before or 
after the code:

    try with block except
    with try block except 
    try with except block

Logically, I don't want this to guard the block. Doing so guards too 
much: it is bad enough when I'm lazy and surround the entire with 
statement in a single try...except, I don't want the language providing 
me a feature specifically to encourage me to do it.

But visually, I would *never* guess that the block was not guarded by 
the with...except clause. Logically, I don't want it to cover the body 
of the with statement, but I hate to imagine having to explain to people 
why it doesn't.

But the alternative is to enshrine in syntax something which *by design* 
guards too much and is a code smell.


-- 
Steve


More information about the Python-ideas mailing list