On Fri, Feb 21, 2014 at 7:46 PM, Chris Angelico <rosuav@gmail.com> wrote:
On Sat, Feb 22, 2014 at 9:06 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
> Nick Coghlan wrote:
>> On 21 February 2014 13:15, Chris Angelico <rosuav@gmail.com> wrote:
>>> 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
>> I'd like to make the case that the PEP should adopt this as its
>> default position.
> I generally agree, but I'd like to point out that this
> doesn't necessarily mean making the parenthesizing rules as
> strict as they are for generator expressions.
> The starting point for genexps is that the parens are part of
> the syntax, the same way that square brackets are part of
> the syntax of a list comprehension; we only allow them to
> be omitted in very special circumstances.
> On the other hand, I don't think there's any harm in allowing
> an except expression to stand on its own when there is no
> risk of ambiguity, e.g.
>    foo = things[i] except IndexError: None
> should be allowed, just as we allow
>    x = a if b else c
> and don't require
>    x = (a if b else c)

I'm inclined to agree with you. I fought against the mandated parens
for a long time. Would be happy to un-mandate them, as long as there's
no way for there to be ambiguity. Is CPython able to make an operator
non-associative? That is, to allow these:

value = expr except Exception: default
value = (expr except Exception: default) except Exception: default
value = expr except Exception: (default except Exception: default)

but not this:

value = expr except Exception: default except Exception: default

Yes, although how to do it depends on what else 'default' and 'expr' can be. The simple answer is to make 'expr' and 'default' be subsets of expressions that don't include the 'except' expression themselves. Just like how in 'A if C else B', 'A' and 'C' do not include the 'if' syntax, but 'B' does: you need parentheses in '(a if c1 else b) if c2 else d' and 'a if (c1 if c3 else c2) else b' but not in 'a if c1 else b if c2 else d'.

However, the question becomes what to do with 'lambda' and 'if-else'. Which of these should be valid (and mean what it means with the parentheses, which is how the PEP currently suggests it should be; ones without parenthesized alternatives are unambiguous, as far as I can tell.)

1. make_adder(x) except TypeError: lambda y: y + 0

2. lambda x: x + 0 except TypeError: 1
-> lambda x: (x + 0 except TypeError: 1)

3. A if f(A) except TypeError: C else B

4. f(A1) except TypeError: A2 if C else B
-> f(A1) except TypeError: (A2 if C else B)

5. f(A1) except TypeError if C else ValueError: f(A2)

6. A if C else f(B) except TypeError: B
-> (A if C else f(B)) except TypeError: B

7. f(A) except E1(A) except E2(A): E2: E1 
-> f(A) except (E1(A) except E2(A): E2): E1

8. f(A) or f(B) except TypeError: f(C)
-> (f(A) or f(B)) except TypeError: f(C)

9. f(A) except TypeError: f(B) or f(C)
-> f(A) except TypeError: (f(B) or f(C))

10. A == f(A) except TypeError: B
-> (A == f(A)) except TypeError: B

11. f(A) except TypeError: A == B
-> f(A) except TypeError: (A == B)

12. A + B except TypeError: C
-> (A + B) except TypeError: C

13. f(A) except TypeError: A + B
-> f(A) except TypeError: (A + B)

#6 in particular and to some extent #4, #8 and #10 look wrong to me, so if they'd be allowed without parentheses perhaps the precedence of if-expr and except should be more nuanced. ('between lambda and ifexpr' is not really a sensible way to describe precedence anyway, since despite the insistence of the reference manual, the precedence of 'lambda' and 'ifexpr' is at best 'mixed': the grammar actually tries to match ifexpr first, but it works the way we all expect because 'lambda' is not an infix operator.)

It's easy to disallow most of these (allowing only their parenthesized versions), the question is where to draw the line :) Some of these require a bit more work in the grammar, duplicating some of the grammar nodes, but it shouldn't be too bad. However, disallowing any infix operators in the 'default' expression means it'll still be valid, just mean something different; #9, for example, would be parsed as '(f(A) except TypeError: f(B)) or f(C)' if the 'default' expression couldn't be an or-expression. Disallowing it in the 'expr' expression wouldn't have that effect without parentheses.

(FWIW, I have a working patch without tests that allows all of these, I'll upload it tonight so people can play with it. Oh, and FWIW, currently I'm +0 on the idea, -0 on the specific syntax.)
Thomas Wouters <thomas@python.org>

Hi! I'm an email virus! Think twice before sending your email to help me spread!