[Python-Dev] PEP 463: Exception-catching expressions

Chris Angelico rosuav at gmail.com
Fri Feb 21 13:42:50 CET 2014


On Fri, Feb 21, 2014 at 10:35 PM, Nick Coghlan <ncoghlan at gmail.com> wrote:
> On 21 February 2014 13:15, Chris Angelico <rosuav at gmail.com> wrote:
>> PEP: 463
>> Title: Exception-catching expressions
> Great work on this Chris - this is one of the best researched and
> justified Python syntax proposals I've seen :)

It is? Wow... I'm not sure what that says about other syntax
proposals. This is one week's python-ideas discussion plus one little
script doing analysis on the standard library. Hardly PhD level
research :)

>> Parentheses around the entire expression
>> ----------------------------------------
>>
>> Generator expressions require parentheses, unless they would be
>> strictly redundant.  Ambiguities with except expressions could be
>> resolved in the same way, forcing nested except-in-except trees to be
>> correctly parenthesized and requiring that the outer expression be
>> clearly delineated.  `Steven D'Aprano elaborates on the issue.`__
>>
>> __ https://mail.python.org/pipermail/python-ideas/2014-February/025647.html
>
> I'd like to make the case that the PEP should adopt this as its
> default position. My rationale is mainly that if we start by requiring
> the parentheses, it's pretty straightforward to take that requirement
> away in specific cases later, as well as making it easier to introduce
> multiple except clauses if that ever seems necessary.
>
> However, if we start without the required parentheses, that's it - we
> can't introduce a requirement for parentheses later if we decide the
> bare form is too confusing in too many contexts, and there's plenty of
> potential for such confusion.

If once the parens are made mandatory, they'll most likely stay
mandatory forever - I can't imagine there being any strong impetus to
change the language to make them optional.

> The required parentheses also help in the cases where there is a
> nearby colon with a different meaning:
>
>    if check() except Exception: False:
>         ...
>    if (check() except Exception: False):

People can already write:

if (x if y else z):

without the parens, and it works. Readability suffers when the same
keyword is used twice (here "if" rather than the colon, but same
difference), yet the parens are considered optional. Python is a
language that, by and large, lacks syntactic salt; style guides are
free to stipulate more, but the language doesn't make demands. I would
strongly *recommend* using parens in all the cases you've shown,
especially lambda:

>    lambda x: calculate(x) except Exception: None
>    lambda x: (calculate(x) except Exception: None)

as it would otherwise depend on operator precedence; but mandating
them feels to me like demanding readability.

>    def f(a: "OS dependent" = os_defaults[os.name] except KeyError: None): pass
>    def f(a: "OS dependent" = (os_defaults[os.name] except KeyError: None)): pass

Ehh, that one's a mess. I'd be looking at breaking out the default:

default = os_defaults[os.name] except KeyError: None
def f(a: "OS dependent" = default): pass

with possibly some better name than 'default'. The one-liner is almost
80 characters long without indentation and with very short names. But
if it's okay to wrap it, that would work without the parens:

def f(
    a: "OS dependent" = os_defaults[os.name] except KeyError: None,
    another_arg .........,
    more, args ......,
):
    pass

Clarity is maintained by judicious placement of newlines just as much
as by parentheses.

> Rather than making people consider "do I need the parentheses in this
> case or not?", adopting the genexp rule makes it simple: yes, you need
> them, because the compiler will complain if you leave them out.

Yes, that is a reasonable line of argument. On the other hand, there's
no demand for parens when you mix and and or:

x or y and z

I'd wager more than half of Python programmers would be unable to say
for sure which would be evaluated first. The compiler could have been
written to reject this (by placing and and or at the same precedence
and having no associativity - I'm not sure if the current lexer in
CPython can do that, but it's certainly not conceptually
inconceivable), but a decision was made to make this legal.

>> Retrieving a message from either a cache or the internet, with auth
>> check::
>>
>>     logging.info("Message shown to user: %s",((cache[k]
>>         except LookupError:
>>             (backend.read(k) except OSError: 'Resource not available')
>>         )
>>         if check_permission(k) else 'Access denied'
>>     ) except BaseException: "This is like a bare except clause")
>
> I don't think taking it all the way to one expression shows the new
> construct in the best light. Keeping this as multiple statements
> assigning to a temporary variable improves the readability quite a
> bit:

Yeah, good point. I tried to strike a balance between simple and
complex examples, but it's hard to judge.

> I would also move the "bare except clause" equivalent out to a
> separate example. Remember, you're trying to convince people to *like*
> the PEP, not scare them away with the consequences of what happens
> when people try to jam too much application logic into a single
> statement. While we're admittedly giving people another tool to help
> them win obfuscated Python contests, we don't have to *encourage* them
> :)

Heh. That example was written when an actual bare except clause was
part of the proposal. I'll drop that entire example; it's contrived,
and we have a number of concrete examples now.

>> Capturing the exception object
>> ------------------------------
>>
>> An examination of the Python standard library shows that, while the use
>> of 'as' is fairly common (occurring in roughly one except clause in five),
>> it is extremely *uncommon* in the cases which could logically be converted
>> into the expression form.  Its few uses can simply be left unchanged.
>> Consequently, in the interests of simplicity, the 'as' clause is not
>> included in this proposal.  A subsequent Python version can add this without
>> breaking any existing code, as 'as' is already a keyword.
>
> We can't defer this one - if we don't implement it now, we should
> reject it as a future addition. The reason we can't defer it is
> [ chomped for brevity ]
>
> So I think it makes more sense to reject this subproposal outright -
> it makes the change more complex and make the handling of the common
> case worse, for the sake of something that will almost never be an
> appropriate thing to do.

Interesting. That's an aspect I never thought of.

Suppose Python were to add support for anonymous sub-scopes. This
could be applied to every current use of 'as', as well as
comprehensions:

spam = "Original spam"
try: 1/0
except Exception as spam:
    assert isinstance(spam, Exception)
with open(fn) as spam:
    assert hasattr(spam, "read")
[spam for spam in [1,2,3]]
assert spam == "Original spam"

It'd be a backward-incompatible change, but it's more likely to be
what people expect. The general assumption of "with ... as ..." is
that the thing should be used inside the block, and should be finished
with when you exit the block, so having the name valid only inside the
block does make sense.

That's a completely separate proposal. But suppose that were to
happen, and to not require a closure. It would then make good sense to
be able to capture an exception inside an expression - and it could be
done without breaking anything.

So, if it is to be rejected, I'd say it's on the technical grounds
that it would require a closure in CPython, and that a closure is
incompatible with the current proposal. Does that sound right?

(It's also not a huge loss, since it's almost unused. But it's an
incompatibility between statement and expression form.)

ChrisA


More information about the Python-Dev mailing list