On Mon, Mar 10, 2014 at 1:16 PM, Jim J. Jewett <jimjjewett@gmail.com> wrote:
On Fri Mar 7 20:54:31 CET 2014, Chris Angelico wrote:
I don't see except expressions as fundamentally more associated with if/else than with, say, an or chain, which works left to right.
I do, because of the skipping portion.
Short-circuiting operators, such as an "or" chain, never skip a clause unless they are skipping *every* subsequent clause.
An "if" statement sometimes skips the (unlabeled in python) "then" clause, but still processes the even-later "else" clause.
A "try" statement sometimes skips the remainder of the try suite but still executes the later subordinate "except" and "finally" clauses.
Note that this only explains why I see "except" as more closely related to "if" than to "or"; it isn't sufficient to justify going back to execute the skipped clause later. That said, going back to a previous location is a lot easier to excuse after an error handler than in "regular" code.
This is a rather tenuous connection, I think. It also doesn't justify a fundamentally out-of-order evaluation. Compare all of these, which are - unless I've made a mistake somewhere - evaluated in the order shown (that is, no lower-numbered will be evaluated after a higher-numbered, but of course not everything will be evaluated): if expr1: stmt2 elif expr3: stmt4 else: stmt5 try: stmt1 except expr2: stmt3 except expr4: stmt5 except: stmt6 else: stmt7 finally: stmt8 value = expr1 or expr2 or expr3 or expr4 value = expr1 and expr2 and expr3 and expr4 # With the current recommendations in PEP 463 value = (expr1 except expr2: expr3) value = expr2 if expr1 else expr3 The if expression is the *only* one that's out of order, and it's justified by (a) "reading" correctly (ie being similar to English), and (b) using two existing keywords, rather than creating a 'then' keyword or using symbols. Useful reading: http://legacy.python.org/dev/peps/pep-0308/ Skip down to the "Detailed Results of Voting" section (sorry, text/plain PEPs don't do hash links) and note that these four proposals received the most support: A. x if C else y B. if C then x else y C. (if C: x else: y) D. C ? x : y with more support for C and D than for A and B. B requires a new keyword, D is all symbols, C is indistinguishable from the statement without the parens, and A puts the expressions in the wrong order. All of them are somewhat flawed but all of them have some merit, too. The BDFL went for A (which proves that Python isn't a democracy), and there's no explanation of the reasoning behind that, but in the absence of a strong statement on the subject, I would say that the gap between the above four options is sufficiently narrow that this can't be used to justify out-of-order syntax in any other proposal.
Analysis of the Python standard library suggests that the single-if situation is *by far* the most common, to the extent that it'd hardly impact the stdlib at all to add multiple except clauses to the proposal. Do you have a strong use-case for the more full syntax?
I do not.
I dislike the arbitrary restriction, and I worry that lifting it later (while maintaining backwards compatibility) will result in a syntax wart, but I do not have a compelling use case for that later relaxation.
That's one reason for the mandatory external parentheses. With some real-world usage data from Python 3.5, if this were implemented exactly per the PEP, a decision could be made in 3.6 to lift one of two restrictions: 3.5: (expr1 except Exception2: expr3) 3.6 choice 1: expr1 except Exception2: expr3 3.6 choice 2: (expr1 except Exception2: expr3 except Exception4: expr5) But these are incompatible. Lifting either restriction mandates the retention of the other. Note also that internal parens are less easily removed - it becomes two distinct code branches. Removing the requirement to have external parents simply means that old-style code is putting parens around an expression, no different from: value = (1 if x else 2) value = (3 + 5) and completely harmless. Allowing optional internal parens means maintaining two syntaxes forever, and both human and computer parsers would have to be compatible with both.
and I strongly prefer that they [the parentheses] be internal (which you fear looks too much like calling a function named except). In that case, it is:
expr1 except (expr3 if expr2)
I'm still not really seeing how this is better.
For one thing, it makes it clear that the "if" keyword may be messing with the order of evaluation.
It still could be waiting for its else. I mean, it's bad code style, but you theoretically could do this: foo[x] except (IndexError if isinstance(foo,list) else KeyError): None Really bad style and horribly unclear (and anyway, you could just catch LookupError), but grammatically legal. Anything using the word 'if' in an expression context has to consider that. (Note that the converse of using 'else' on its own wouldn't be a problem, as it wouldn't be following an if.)
I don't claim that syntax is perfect. I do think it is less flawed than the no-parentheses (or external parentheses) versions:
(expr1 except expr3 if expr2) expr1 except expr3 if expr2
because the tigher parentheses correctly indicate that expr2 and expr3 should be considered as a (what-to-do-in-case-of-error) group, which interacts (as a single unit) with the main expression.
But it doesn't, really. The entire set of three expressions is a single unit. You can't break out the bit inside the parens and give that a name, like you can in most places where something "acts as a single unit" to interact with something else. (Yes, there are special cases, like the syntax for constructing slice objects that works only inside square brackets. And you can't break out a function's arguments, as a unit, into a single object (the nearest is *args,**kw). I said most places, and I don't want to add more to the special-case set.)
which in turn is far, far better than the colon versions with external or missing parentheses:
(expr1 except expr2: expr3) expr1 except expr2: expr3
because I cannot imagine reading an embedded version of either of those without having to mentally re-parse at the colon.
What do you mean by "re-parse"? The general recommendation is that the entire except expression should fit inside a human's 7 +/- 2 limit. Fundamentally, a good use of this syntax will still be saying basically the same thing at the end that it seemed to be saying at the beginning, in the same way that short-circuiting or can be used.
dic = {7:"Seven", 9:None, 5:"Five"} dic[7] or "Default" 'Seven' dic[9] or "Default" 'Default'
It's still looking something up, but now it adds a default for the case where the stored value is falsy. Now compare the (slightly more verbose) version that catches an exception:
(dic[3] except KeyError: "Not present") 'Not present'
Again, it's still looking something up, but now coping with an additional situation.
I've had more trouble with comma vs period than with different types of bracket. But let's assume that there is confusion, and someone sees:
expr1 except {expr2:expr3}
(most fonts will make square brackets clear enough, it's only braces and round brackets that are problematic)
These are not yet defined any more than the tuple-with-colon version is, nor do they have an obvious-yet-incompatible meaning. In fact, I would prefer either of them to any version that does not syntactically associate the exception list with the result those exceptions imply.
Actually, braces with colons inside *is* defined. What's not defined is the word "except" followed by a dictionary. But at the point where you're reading that, it looks like a dict. The trouble is that that's *almost* valid; you can think of the exception handling as mapping an exception to its result. But that's not valid for two reasons: firstly, exception handling is inherently ordered, and secondly, the expressions are not evaluated eagerly, as they would be in a dict. Only the one that matches will be evaluated. Making the syntax look like it's laying out a dictionary would be awkwardly confusing.
The colon is acceptable only to the extent that this similarity does help. So if a colon is used, I want the similarity to be as strong as possible -- which, I suppose, is another argument for tightening the parentheses, and possibly an argument for using [] instead of ().
Tightening them doesn't particularly help, but is an arguable point. But using square brackets? Definitely not. Parentheses are used syntactically in many places (grouping, function calls, and they're associated with tuples), but square brackets are not; plus, see argument above about the option of relaxing the restrictions. Using square brackets would be confusing, and would often be interpreted as creating a list. Not something we want to imply. ChrisA