On Fri, Feb 21, 2014 at 10:35 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 21 February 2014 13:15, Chris Angelico <rosuav@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