Hi,
Here's an idea that would help shortening code. Allow a ternary expression based on except, like so:
first_entry = entries[0] except IndexError else None item = my_queue.get() except queue.Empty else None response_text = request('http://whatever.com%27).text except HttpError else "Can't access data"
Aside from the fact that this would be a big grammar addition, a big problem here is the usage of the `else` keyword, that when used with except usually means "what would happen if there wasn't an exception" and here means the opposite. But I couldn't think of a nicer syntax.
I realize that this is a big change and that most people would be opposed to this... But I guess I just wanted to share my idea :)
Ram.
On 2014-02-12 21:02, Ram Rachum wrote:
Hi,
Here's an idea that would help shortening code. Allow a ternary expression based on except, like so:
first_entry = entries[0] except IndexError else None item = my_queue.get() except queue.Empty else None response_text = request('http://whatever.com').text except HttpError else "Can't access data"
Aside from the fact that this would be a big grammar addition, a big problem here is the usage of the `else` keyword, that when used with except usually means "what would happen if there wasn't an exception" and here means the opposite. But I couldn't think of a nicer syntax.
If you don't mind having a colon in the middle of an expression:
first_entry = entries[0] except IndexError: None item = my_queue.get() except queue.Empty: None response_text = request('http://whatever.com%27).text except HttpError: "Can't access data"
What would its precedence be? Maybe it would apply to the preceding expression or subexpression:
total = (entries[0] except IndexError: 0) + (entries[-1] except IndexError: 0)
I realize that this is a big change and that most people would be opposed to this... But I guess I just wanted to share my idea :)
On Feb 12, 2014, at 9:02 PM, Ram Rachum ram.rachum@gmail.com wrote:
Here's an idea that would help shortening code. Allow a ternary expression based on except, like so:
first_entry = entries[0] except IndexError else None item = my_queue.get() except queue.Empty else None response_text = request('http://whatever.com').text except HttpError else "Can't access data"
Aside from the fact that this would be a big grammar addition, a big problem here is the usage of the `else` keyword, that when used with except usually means "what would happen if there wasn't an exception" and here means the opposite. But I couldn't think of a nicer syntax.
I realize that this is a big change and that most people would be opposed to this... But I guess I just wanted to share my idea :)
I would like to see something like this come to fruition. We need a clean way to express the idea of "take an arbitrary, exception-raising function and give it a default argument".
Hopefully, this would end the gradual but never-ending requests to bloat APIs with "default" arguments. For example, if your idea or some variant had been in place, the min() and max() functions likely wouldn't have grown complex signatures in Python 3.4.
Raymond
I'm happy you're for it. Maybe one day we'll see a Python 4 with no second argument to dict.get, getattr and so many others...
On Thu, Feb 13, 2014 at 2:08 AM, Raymond Hettinger < raymond.hettinger@gmail.com> wrote:
On Feb 12, 2014, at 9:02 PM, Ram Rachum ram.rachum@gmail.com wrote:
Here's an idea that would help shortening code. Allow a ternary expression based on except, like so:
first_entry = entries[0] except IndexError else None item = my_queue.get() except queue.Empty else None response_text = request('http://whatever.com').text except HttpError
else "Can't access data"
Aside from the fact that this would be a big grammar addition, a big problem here is the usage of the `else` keyword, that when used with except usually means "what would happen if there wasn't an exception" and here means the opposite. But I couldn't think of a nicer syntax.
I realize that this is a big change and that most people would be opposed to this... But I guess I just wanted to share my idea :)
I would like to see something like this come to fruition. We need a clean way to express the idea of "take an arbitrary, exception-raising function and give it a default argument".
Hopefully, this would end the gradual but never-ending requests to bloat APIs with "default" arguments. For example, if your idea or some variant had been in place, the min() and max() functions likely wouldn't have grown complex signatures in Python 3.4.
Raymond
Totally agree with these sentiments. Another advantage is that this code is self-documenting. These "defaulting signatures" usually benefit from a comment to remind the reader what's going on. With the except clause, it's obvious.
On Wednesday, February 12, 2014 7:14:45 PM UTC-5, Ram Rachum wrote:
I'm happy you're for it. Maybe one day we'll see a Python 4 with no second argument to dict.get, getattr and so many others...
On Thu, Feb 13, 2014 at 2:08 AM, Raymond Hettinger <raymond....@gmail.comjavascript:
wrote:
On Feb 12, 2014, at 9:02 PM, Ram Rachum <ram.r...@gmail.com javascript:> wrote:
Here's an idea that would help shortening code. Allow a ternary expression based on except, like so:
first_entry = entries[0] except IndexError else None item = my_queue.get() except queue.Empty else None response_text = request('http://whatever.com').text except HttpError
else "Can't access data"
Aside from the fact that this would be a big grammar addition, a big problem here is the usage of the `else` keyword, that when used with except usually means "what would happen if there wasn't an exception" and here means the opposite. But I couldn't think of a nicer syntax.
I realize that this is a big change and that most people would be opposed to this... But I guess I just wanted to share my idea :)
I would like to see something like this come to fruition. We need a clean way to express the idea of "take an arbitrary, exception-raising function and give it a default argument".
Hopefully, this would end the gradual but never-ending requests to bloat APIs with "default" arguments. For example, if your idea or some variant had been in place, the min() and max() functions likely wouldn't have grown complex signatures in Python 3.4.
Raymond
Ram Rachum ram.rachum@gmail.com writes:
Here's an idea that would help shortening code. Allow a ternary expression based on except, like so:
first_entry = entries[0] except IndexError else None item = my_queue.get() except queue.Empty else None response_text = request('http://whatever.com').text except HttpError else "Can't access data"
That is more obscure, to my eye, than laying out the control branches:
first_entry = entries[0] except IndexError else None item = my_queue.get() except queue.Empty else None try: response_text = request('http://whatever.com%27).text except HttpError: "Can't access data"
Do you have some real-use code that your proposal would significantly improve? I find your example to support the refusal of that syntax.
Ben Finney ben+python@benfinney.id.au writes:
Ram Rachum ram.rachum@gmail.com writes:
Here's an idea that would help shortening code. Allow a ternary expression based on except, like so:
first_entry = entries[0] except IndexError else None item = my_queue.get() except queue.Empty else None response_text = request('http://whatever.com').text except HttpError else "Can't access data"
That is more obscure, to my eye, than laying out the control branches:
Sorry, I failed to address your first two examples.
I am +0 on the proposal to have something similar to Perl's fallback syntax, “$foo = bar() or some_default_value”.
Yet I still find the proposed syntax less readable for anything but a trivial *and* brief case. For anything longer than a few dozen characters, I still prefer::
try: response_text = request('http://whatever.com%27).text except HttpError: "Can't access data"
for being explicit and clarifying what to expect.
From: Ben Finney ben+python@benfinney.id.au
Sent: Wednesday, February 12, 2014 4:28 PM
Subject: Re: [Python-ideas] except expression
Ben Finney ben+python@benfinney.id.au writes:
Ram Rachum ram.rachum@gmail.com writes:
Here's an idea that would help shortening code. Allow a ternary expression based on except, like so:
first_entry = entries[0] except IndexError else None item = my_queue.get() except queue.Empty else None response_text = request('http://whatever.com%27).text except
HttpError else "Can't access data"
That is more obscure, to my eye, than laying out the control branches:
Sorry, I failed to address your first two examples.
I am +0 on the proposal to have something similar to Perl's fallback syntax, “$foo = bar() or some_default_value”.
Although this looks nicer than the original proposal, it loses the ability to specify what exception you want to catch. And I think it would be reasonable to want to, e.g., handle queue.Empty but not swallow an AttributeError caused by a typo… On the other hand, I really dislike the misuse of else in the original version. But the syntax can be bikeshedded, and probably has been before.
I have one reservation: Given that so many functions in Python take a "default value" parameter, could this lead to making the language less predictable and consistent? For some expressions, you write "d.get(key, defval)", while for others you write "d.get(key) except KeyError else defval", and it may not be obvious which are which. And I don't think the answer is to remove all those default values. The d.get(key, defval) is much more concise and a bit more readable, and removes the opportunity to, e.g., screw up and use the wrong exception type.
But other than that, if someone can come up with a readable way to write it, I like the idea.
Yet I still find the proposed syntax less readable for anything but a trivial *and* brief case.
I agree—but the same is true for pretty much all expression syntax, and most of it is rarely misused.
Consider if-else ternary expressions. It's very easy to make your code completely unreadable by chaining them together, or using complex test expressions, or using them in the middle of a comprehension, etc. But you rarely see such code. And meanwhile, you see a lot of code that's more concise and readable because it uses trivial if expressions. I think that except expressions would go the same way.
For anything longer than a few dozen
characters, I still prefer::
try: response_text = request('http://whatever.com%27).text except HttpError: "Can't access data" for being explicit and clarifying what to expect.
Except that you're not doing the same thing as the original; you're just evaluating and throwing away the string literal, and not assigning anything to response_text. Having to write "response_text = " twice gives you twice as many places to screw up—as evidenced by the fact that you did so. The exact same thing happens with if statements vs. if expressions, and in fact I think that's one of the reasons people like if expressions.
Why not use yield instead of else?
foo = something() except BazException yield "bar" On Feb 12, 2014 5:56 PM, "Andrew Barnert" abarnert@yahoo.com wrote:
From: Ben Finney ben+python@benfinney.id.au
Sent: Wednesday, February 12, 2014 4:28 PM
Subject: Re: [Python-ideas] except expression
Ben Finney ben+python@benfinney.id.au writes:
Ram Rachum ram.rachum@gmail.com writes:
Here's an idea that would help shortening code. Allow a ternary expression based on except, like so:
first_entry = entries[0] except IndexError else None item = my_queue.get() except queue.Empty else None response_text = request('http://whatever.com').text except
HttpError else "Can't access data"
That is more obscure, to my eye, than laying out the control branches:
Sorry, I failed to address your first two examples.
I am +0 on the proposal to have something similar to Perl's fallback syntax, “$foo = bar() or some_default_value”.
Although this looks nicer than the original proposal, it loses the ability to specify what exception you want to catch. And I think it would be reasonable to want to, e.g., handle queue.Empty but not swallow an AttributeError caused by a typo… On the other hand, I really dislike the misuse of else in the original version. But the syntax can be bikeshedded, and probably has been before.
I have one reservation: Given that so many functions in Python take a "default value" parameter, could this lead to making the language less predictable and consistent? For some expressions, you write "d.get(key, defval)", while for others you write "d.get(key) except KeyError else defval", and it may not be obvious which are which. And I don't think the answer is to remove all those default values. The d.get(key, defval) is much more concise and a bit more readable, and removes the opportunity to, e.g., screw up and use the wrong exception type.
But other than that, if someone can come up with a readable way to write it, I like the idea.
Yet I still find the proposed syntax less readable for anything but a trivial *and* brief case.
I agree—but the same is true for pretty much all expression syntax, and most of it is rarely misused.
Consider if-else ternary expressions. It's very easy to make your code completely unreadable by chaining them together, or using complex test expressions, or using them in the middle of a comprehension, etc. But you rarely see such code. And meanwhile, you see a lot of code that's more concise and readable because it uses trivial if expressions. I think that except expressions would go the same way.
For anything longer than a few dozen
characters, I still prefer::
try: response_text = request('http://whatever.com').text except HttpError: "Can't access data"
for being explicit and clarifying what to expect.
Except that you're not doing the same thing as the original; you're just evaluating and throwing away the string literal, and not assigning anything to response_text. Having to write "response_text = " twice gives you twice as many places to screw up—as evidenced by the fact that you did so. The exact same thing happens with if statements vs. if expressions, and in fact I think that's one of the reasons people like if expressions. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Thu, Feb 13, 2014 at 2:08 PM, Amber Yust amber.yust@gmail.com wrote:
Why not use yield instead of else?
foo = something() except BazException yield "bar"
yield is already an expression. It'd be theoretically and syntactically valid (if a little weird) to use yield "bar" in place of the name BazException; you'd yield "bar" to your caller, then whatever exception gets sent in would be the one tested for. I honestly cannot conceive of any situation where this would actually be useful, but it does make it a little tricky to reuse that keyword :)
ChrisA
Ah, that's a good point (the two-directionality of yield had slipped my mind). I had considered suggesting return instead of yield, which wouldn't have that problem, but it felt like return would be more confusing to see in a context where it doesn't actually return from the enclosing scope. On Feb 12, 2014 7:16 PM, "Chris Angelico" rosuav@gmail.com wrote:
On Thu, Feb 13, 2014 at 2:08 PM, Amber Yust amber.yust@gmail.com wrote:
Why not use yield instead of else?
foo = something() except BazException yield "bar"
yield is already an expression. It'd be theoretically and syntactically valid (if a little weird) to use yield "bar" in place of the name BazException; you'd yield "bar" to your caller, then whatever exception gets sent in would be the one tested for. I honestly cannot conceive of any situation where this would actually be useful, but it does make it a little tricky to reuse that keyword :)
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Another possible option:
foo = something() except None for BarException
With possible support for:
foo = something() except e.message for BarException as e On Feb 12, 2014 7:20 PM, "Amber Yust" amber.yust@gmail.com wrote:
Ah, that's a good point (the two-directionality of yield had slipped my mind). I had considered suggesting return instead of yield, which wouldn't have that problem, but it felt like return would be more confusing to see in a context where it doesn't actually return from the enclosing scope. On Feb 12, 2014 7:16 PM, "Chris Angelico" rosuav@gmail.com wrote:
On Thu, Feb 13, 2014 at 2:08 PM, Amber Yust amber.yust@gmail.com wrote:
Why not use yield instead of else?
foo = something() except BazException yield "bar"
yield is already an expression. It'd be theoretically and syntactically valid (if a little weird) to use yield "bar" in place of the name BazException; you'd yield "bar" to your caller, then whatever exception gets sent in would be the one tested for. I honestly cannot conceive of any situation where this would actually be useful, but it does make it a little tricky to reuse that keyword :)
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Even more generally (not sure allowing multiple clauses is a good idea but at least "if" sounds better than "for", I think. foo = bar() except e.attr1 if FooException as e else e.attr2 if BarException as e
Antony Lee
2014-02-12 19:25 GMT-08:00 Amber Yust amber.yust@gmail.com:
Another possible option:
foo = something() except None for BarException
With possible support for:
foo = something() except e.message for BarException as e On Feb 12, 2014 7:20 PM, "Amber Yust" amber.yust@gmail.com wrote:
Ah, that's a good point (the two-directionality of yield had slipped my mind). I had considered suggesting return instead of yield, which wouldn't have that problem, but it felt like return would be more confusing to see in a context where it doesn't actually return from the enclosing scope. On Feb 12, 2014 7:16 PM, "Chris Angelico" rosuav@gmail.com wrote:
On Thu, Feb 13, 2014 at 2:08 PM, Amber Yust amber.yust@gmail.com wrote:
Why not use yield instead of else?
foo = something() except BazException yield "bar"
yield is already an expression. It'd be theoretically and syntactically valid (if a little weird) to use yield "bar" in place of the name BazException; you'd yield "bar" to your caller, then whatever exception gets sent in would be the one tested for. I honestly cannot conceive of any situation where this would actually be useful, but it does make it a little tricky to reuse that keyword :)
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
That has a potential conflict in parsing, however, since the if-else ternary already exists. Yes, the 'as' theoretically disambiguates it for the machine parser, but for a human trying to parse it as they read, it's not nearly as simple.
The reason why I suggested 'for' is that it still flows somewhat naturally - it's using "for" in the sense of "associated with" or "in the place of". "I'll give you my None for your FooException."
On Wed Feb 12 2014 at 9:46:37 PM, Antony Lee antony.lee@berkeley.edu wrote:
Even more generally (not sure allowing multiple clauses is a good idea but at least "if" sounds better than "for", I think. foo = bar() except e.attr1 if FooException as e else e.attr2 if BarException as e
Antony Lee
2014-02-12 19:25 GMT-08:00 Amber Yust amber.yust@gmail.com:
Another possible option:
foo = something() except None for BarException
With possible support for:
foo = something() except e.message for BarException as e On Feb 12, 2014 7:20 PM, "Amber Yust" amber.yust@gmail.com wrote:
Ah, that's a good point (the two-directionality of yield had slipped my mind). I had considered suggesting return instead of yield, which wouldn't have that problem, but it felt like return would be more confusing to see in a context where it doesn't actually return from the enclosing scope. On Feb 12, 2014 7:16 PM, "Chris Angelico" rosuav@gmail.com wrote:
On Thu, Feb 13, 2014 at 2:08 PM, Amber Yust amber.yust@gmail.com wrote:
Why not use yield instead of else?
foo = something() except BazException yield "bar"
yield is already an expression. It'd be theoretically and syntactically valid (if a little weird) to use yield "bar" in place of the name BazException; you'd yield "bar" to your caller, then whatever exception gets sent in would be the one tested for. I honestly cannot conceive of any situation where this would actually be useful, but it does make it a little tricky to reuse that keyword :)
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 02/13/2014 06:53 AM, Amber Yust wrote:
The reason why I suggested 'for' is that it still flows somewhat naturally
- it's using "for" in the sense of "associated with" or "in the place of".
"I'll give you my None for your FooException."
True, but it is probably not wise to use a natural language preposition in two different syntactic schemas with two different meanings: here 'for' meaning either traversal loop or "expression-exception-condition" (would be ok if the meaning was the same).
d
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
One use case, for example, is handing IndexError when retrieving an item from a sequence (which currently has no nice standard spelling, and isn't amenable to the "pass in a default answer" solution because it isn't a normal function call).
Another case not handled well by the status quo is when the default answer is expensive to calculate for some reason, so you really only want to calculate it if you actually need it.
Unfortunately, like PEP 308 before it, the hard part is coming up with a reasonable spelling that won't have people breaking out the torches and pitchforks if the PEP is accepted.
On 13 Feb 2014 13:34, "Amber Yust" amber.yust@gmail.com wrote:
Another possible option:
foo = something() except None for BarException
With possible support for:
foo = something() except e.message for BarException as e
This is also one of the possible spellings I came up with, and it is my current least disliked option. The main concern I have with it is the substantially different interpretation it gives to the "for" keyword - as far as is practical, we try to ensure that a given keyword relates to a consistent concept, and the link to iteration is rather tenuous here (it's only present in the fact you can use an iterable of exception types rather than just one). Aside from that concern, I think it scores well on the readability front.
"if" would be better, but, as you already noted, poses significant parsing challenges (since it likely wouldn't be easy to require that ternary expressions use parentheses in this new construct, but still allow the parentheses to be omitted in the general case).
Cheers, Nick.
On 13 Feb 2014 19:24, "Nick Coghlan" ncoghlan@gmail.com wrote:
General comment: like Raymond, I'm inclined to favour a nice expression
friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
One use case, for example, is handing IndexError when retrieving an item
from a sequence (which currently has no nice standard spelling, and isn't amenable to the "pass in a default answer" solution because it isn't a normal function call).
Another case not handled well by the status quo is when the default
answer is expensive to calculate for some reason, so you really only want to calculate it if you actually need it.
Unfortunately, like PEP 308 before it, the hard part is coming up with a
reasonable spelling that won't have people breaking out the torches and pitchforks if the PEP is accepted.
On 13 Feb 2014 13:34, "Amber Yust" amber.yust@gmail.com wrote:
Another possible option:
foo = something() except None for BarException
With possible support for:
foo = something() except e.message for BarException as e
This is also one of the possible spellings I came up with, and it is my
current least disliked option. The main concern I have with it is the substantially different interpretation it gives to the "for" keyword - as far as is practical, we try to ensure that a given keyword relates to a consistent concept, and the link to iteration is rather tenuous here (it's only present in the fact you can use an iterable of exception types rather than just one). Aside from that concern, I think it scores well on the readability front.
"if" would be better, but, as you already noted, poses significant
parsing challenges (since it likely wouldn't be easy to require that ternary expressions use parentheses in this new construct, but still allow the parentheses to be omitted in the general case).
"from" is another keyword choice worth considering here - that already has no strong semantics of its own ("from x import y" and "yield from iter" derive their meaning from the other keyword involved)
Cheers, Nick.
Cheers, Nick.
On 13.02.2014 10:24, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
Here's a variant the resembles the code you'd write in a helper function to achieve the same thing, only stripped down somewhat:
x = something() except ValueError return default_value
def try_something(): try: return something() except ValueError: return default_value
x = something() except ValueError as exc return exc.message
def try_something(): try: return something() except ValueError as exc return exc.message
Obviously, having a keyword "use" would make a better fit :-)
x = something() except ValueError use default_value
On 13 February 2014 20:10, M.-A. Lemburg mal@egenix.com wrote:
On 13.02.2014 10:24, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
Here's a variant the resembles the code you'd write in a helper function to achieve the same thing, only stripped down somewhat:
x = something() except ValueError return default_value
def try_something(): try: return something() except ValueError: return default_value
x = something() except ValueError as exc return exc.message
def try_something(): try: return something() except ValueError as exc return exc.message
Obviously, having a keyword "use" would make a better fit :-)
x = something() except ValueError use default_value
Even if we don't agree on a resolution for 3.5, I think there's more than enough interest for it to be worth someone's while to collate some of the proposals in a PEP - if nothing else, it will save rehashing the whole discussion next time it comes up :)
The benefits of this:
- the status quo is that various APIs are growing "default" parameters to handle the case where they would otherwise throw an exception - this is creating inconsistencies, as some such functions can be used easily as expressions without risking any exception (those where such a parameter has been added), as well as a temptation to use "Look Before You Leap" pre-checks, even in cases where exception handling would be a better choice - sequence indexing is a case where there is no current standard mechanism for providing a default value, so you're either use a pre-check for the system length, or else using a full try statement or context manager to handle the IndexError - by providing a clean, expression level syntax for handling a single except clause and providing an alternate value for the expression, this problem could be solved once and for all in a systematic way, rather than needing to incrementally change the API of a variety of functions (as well as addressing the container subscripting case in a way that doesn't require switching away from using the subscript syntax to a normal function call, or switching from use an expression to a statement)
Some of the specific syntactic proposals:
x = op() except default if Exception x = op() except default for Exception x = op() except default from Exception x = op() except Exception return default
x = op() except exc.attr if Exception as exc x = op() except exc.attr for Exception as exc x = op() except exc.attr from Exception as exc x = op() except Exception as exc return exc.attr
The except/if construct has parser ambiguity issues. While they're potentially solvable by requiring parens around conditional expressions in that context, that would come at the cost of a significant redesign of the language grammar.
The except/for option reads quite nicely, but introduces a substantially different meaning for "for".
The except/from option reads OK when combined with "as" and actually using the caught exception, but otherwise reads strangely.
The except/return option looks like it should either introduce a new scope or else return from the current function. The presence of the "as exc" clause in all variants actually suggests a new scope could be a good idea, given past experience with iteration variables in list comprehensions.
So, if we take the point of view that the new syntax is almost *literally* a shorthand for:
def _helper(op, exc, make_default): try: return op() except exc: return make_default()
x = _helper(op, Exception, make_default)
Then that would suggest the following syntax and interpretation:
op() except Exception pass
def _work_or_return_None(): try: return op() except Exception: pass _work_or_return_None()
x = op() except Exception return value
def _work_or_return_default(): try: return op() except Exception: return value x = _work_or_return_default()
x = op() except Exception as exc return exc.attr
def _work_or_process_exception(): try: return op() except Exception as exc: return exc.attr x = _work_or_process_exception()
OK, with the "introduces a new scope" caveat, consider me a fan of MAL's syntax unless/until someone points out a potential flaw that I have missed.
A possible addition: allow "raise" in addition to "pass" and "return" (for exception transformation as an expression)
Cheers, Nick.
actually i like the first proposal, because it mirrors “the trinary”:
```python 'spam' if mustard else 'eggs' eat() except SickException else puke() ```
but it’s a bit weird.
I don’t like `return` at all, because it’s exclusively about returning from functions. `for` is for loops. And qhile from is used more versatile, it’s even weirder than `else` here.
I also don’t like putting anythiing but the exception type after the `except`.
It should definitely be `EXPR except EXC_TYPE [as EXC_NAME] KEYWORD ALT_EXPR`, with `EXC_NAME` being defined in `ALT_EXPR` and afterwards.
and i’m leaning towards `pass` or `else` as `KEYWORD`. `pass` is only used to mean “do nothing” right now, and indeed that’s fitting: it’s just a border between `EXC_TYPE [as EXC_NAME]` and `ALT_EXPR`, and its other meaning makes sense in english grammar. Maybe even `try`? Like “use this, except if that doesn’t work, then try using the other thing”.
2014-02-13 13:50 GMT+01:00 Nick Coghlan ncoghlan@gmail.com:
On 13 February 2014 20:10, M.-A. Lemburg mal@egenix.com wrote:
On 13.02.2014 10:24, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the
proliferation
of relatively ad hoc alternative solutions (in particular, the
popularity
of being able to pass in default values to handle empty iterables).
Here's a variant the resembles the code you'd write in a helper function to achieve the same thing, only stripped down somewhat:
x = something() except ValueError return default_value
def try_something(): try: return something() except ValueError: return default_value
x = something() except ValueError as exc return exc.message
def try_something(): try: return something() except ValueError as exc return exc.message
Obviously, having a keyword "use" would make a better fit :-)
x = something() except ValueError use default_value
Even if we don't agree on a resolution for 3.5, I think there's more than enough interest for it to be worth someone's while to collate some of the proposals in a PEP - if nothing else, it will save rehashing the whole discussion next time it comes up :)
The benefits of this:
- the status quo is that various APIs are growing "default" parameters
to handle the case where they would otherwise throw an exception
- this is creating inconsistencies, as some such functions can be used
easily as expressions without risking any exception (those where such a parameter has been added), as well as a temptation to use "Look Before You Leap" pre-checks, even in cases where exception handling would be a better choice
- sequence indexing is a case where there is no current standard
mechanism for providing a default value, so you're either use a pre-check for the system length, or else using a full try statement or context manager to handle the IndexError
- by providing a clean, expression level syntax for handling a single
except clause and providing an alternate value for the expression, this problem could be solved once and for all in a systematic way, rather than needing to incrementally change the API of a variety of functions (as well as addressing the container subscripting case in a way that doesn't require switching away from using the subscript syntax to a normal function call, or switching from use an expression to a statement)
Some of the specific syntactic proposals:
x = op() except default if Exception x = op() except default for Exception x = op() except default from Exception x = op() except Exception return default x = op() except exc.attr if Exception as exc x = op() except exc.attr for Exception as exc x = op() except exc.attr from Exception as exc x = op() except Exception as exc return exc.attr
The except/if construct has parser ambiguity issues. While they're potentially solvable by requiring parens around conditional expressions in that context, that would come at the cost of a significant redesign of the language grammar.
The except/for option reads quite nicely, but introduces a substantially different meaning for "for".
The except/from option reads OK when combined with "as" and actually using the caught exception, but otherwise reads strangely.
The except/return option looks like it should either introduce a new scope or else return from the current function. The presence of the "as exc" clause in all variants actually suggests a new scope could be a good idea, given past experience with iteration variables in list comprehensions.
So, if we take the point of view that the new syntax is almost *literally* a shorthand for:
def _helper(op, exc, make_default): try: return op() except exc: return make_default() x = _helper(op, Exception, make_default)
Then that would suggest the following syntax and interpretation:
op() except Exception pass def _work_or_return_None(): try: return op() except Exception: pass _work_or_return_None() x = op() except Exception return value def _work_or_return_default(): try: return op() except Exception: return value x = _work_or_return_default() x = op() except Exception as exc return exc.attr def _work_or_process_exception(): try: return op() except Exception as exc: return exc.attr x = _work_or_process_exception()
OK, with the "introduces a new scope" caveat, consider me a fan of MAL's syntax unless/until someone points out a potential flaw that I have missed.
A possible addition: allow "raise" in addition to "pass" and "return" (for exception transformation as an expression)
Cheers, Nick.
-- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
My favorite syntax so far is:
x except Exception pass y
x except Exception as e pass e.y
As weird as it is to use pass this way, it's better than using return. It's better than not having the exception immediately follow `except`, it's better than using a colon, it's better than all the other possible compromises we could make.
On Thu, Feb 13, 2014 at 3:19 PM, Philipp A. flying-sheep@web.de wrote:
actually i like the first proposal, because it mirrors "the trinary":
'spam' if mustard else 'eggs' eat() except SickException else puke()
but it's a bit weird.
I don't like `return` at all, because it's exclusively about returning from functions. `for` is for loops. And qhile from is used more versatile, it's even weirder than `else` here.
I also don't like putting anythiing but the exception type after the `except`.
It should definitely be `EXPR except EXC_TYPE [as EXC_NAME] KEYWORD ALT_EXPR`, with `EXC_NAME` being defined in `ALT_EXPR` and afterwards.
and i'm leaning towards `pass` or `else` as `KEYWORD`. `pass` is only used to mean "do nothing" right now, and indeed that's fitting: it's just a border between `EXC_TYPE [as EXC_NAME]` and `ALT_EXPR`, and its other meaning makes sense in english grammar. Maybe even `try`? Like "use this, except if that doesn't work, then try using the other thing".
2014-02-13 13:50 GMT+01:00 Nick Coghlan ncoghlan@gmail.com:
On 13 February 2014 20:10, M.-A. Lemburg mal@egenix.com wrote:
On 13.02.2014 10:24, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the
proliferation
of relatively ad hoc alternative solutions (in particular, the
popularity
of being able to pass in default values to handle empty iterables).
Here's a variant the resembles the code you'd write in a helper function to achieve the same thing, only stripped down somewhat:
x = something() except ValueError return default_value
def try_something(): try: return something() except ValueError: return default_value
x = something() except ValueError as exc return exc.message
def try_something(): try: return something() except ValueError as exc return exc.message
Obviously, having a keyword "use" would make a better fit :-)
x = something() except ValueError use default_value
Even if we don't agree on a resolution for 3.5, I think there's more than enough interest for it to be worth someone's while to collate some of the proposals in a PEP - if nothing else, it will save rehashing the whole discussion next time it comes up :)
The benefits of this:
- the status quo is that various APIs are growing "default" parameters
to handle the case where they would otherwise throw an exception
- this is creating inconsistencies, as some such functions can be used
easily as expressions without risking any exception (those where such a parameter has been added), as well as a temptation to use "Look Before You Leap" pre-checks, even in cases where exception handling would be a better choice
- sequence indexing is a case where there is no current standard
mechanism for providing a default value, so you're either use a pre-check for the system length, or else using a full try statement or context manager to handle the IndexError
- by providing a clean, expression level syntax for handling a single
except clause and providing an alternate value for the expression, this problem could be solved once and for all in a systematic way, rather than needing to incrementally change the API of a variety of functions (as well as addressing the container subscripting case in a way that doesn't require switching away from using the subscript syntax to a normal function call, or switching from use an expression to a statement)
Some of the specific syntactic proposals:
x = op() except default if Exception x = op() except default for Exception x = op() except default from Exception x = op() except Exception return default x = op() except exc.attr if Exception as exc x = op() except exc.attr for Exception as exc x = op() except exc.attr from Exception as exc x = op() except Exception as exc return exc.attr
The except/if construct has parser ambiguity issues. While they're potentially solvable by requiring parens around conditional expressions in that context, that would come at the cost of a significant redesign of the language grammar.
The except/for option reads quite nicely, but introduces a substantially different meaning for "for".
The except/from option reads OK when combined with "as" and actually using the caught exception, but otherwise reads strangely.
The except/return option looks like it should either introduce a new scope or else return from the current function. The presence of the "as exc" clause in all variants actually suggests a new scope could be a good idea, given past experience with iteration variables in list comprehensions.
So, if we take the point of view that the new syntax is almost *literally* a shorthand for:
def _helper(op, exc, make_default): try: return op() except exc: return make_default() x = _helper(op, Exception, make_default)
Then that would suggest the following syntax and interpretation:
op() except Exception pass def _work_or_return_None(): try: return op() except Exception: pass _work_or_return_None() x = op() except Exception return value def _work_or_return_default(): try: return op() except Exception: return value x = _work_or_return_default() x = op() except Exception as exc return exc.attr def _work_or_process_exception(): try: return op() except Exception as exc: return exc.attr x = _work_or_process_exception()
OK, with the "introduces a new scope" caveat, consider me a fan of MAL's syntax unless/until someone points out a potential flaw that I have missed.
A possible addition: allow "raise" in addition to "pass" and "return" (for exception transformation as an expression)
Cheers, Nick.
-- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ --
You received this message because you are subscribed to a topic in the Google Groups "python-ideas" group. To unsubscribe from this topic, visit https://groups.google.com/d/topic/python-ideas/ZoBGdwuH3uk/unsubscribe. To unsubscribe from this group and all its topics, send an email to python-ideas+unsubscribe@googlegroups.com. For more options, visit https://groups.google.com/groups/opt_out.
Ram Rachum writes:
My favorite syntax so far is:
x except Exception as e pass e.y
I think that this syntax is overengineering (regardless of the keywords used).
That is, I think it's reasonable to have a syntax like
> x except Exception pass y
for the purpose of passing a specific default (especially because it's a big step toward getting rid of the nuisance default parameters in many APIs). But the extended syntax here allows e.y to be the result of an arbitrary calculation. I think it's better to use the heavier statement syntax in that case. While I'm no authority on "Pythonicity", somehow packing so much into the syntax
x except Exception as e pass e.y
seems to the be kind of thing that the Zen refers to as "complicated".
On 13/02/2014 12:50, Nick Coghlan wrote:
On 13 February 2014 20:10, M.-A. Lemburg mal@egenix.com wrote:
On 13.02.2014 10:24, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
Here's a variant the resembles the code you'd write in a helper function to achieve the same thing, only stripped down somewhat:
x = something() except ValueError return default_value
def try_something(): try: return something() except ValueError: return default_value
x = something() except ValueError as exc return exc.message
def try_something(): try: return something() except ValueError as exc return exc.message
Obviously, having a keyword "use" would make a better fit :-)
x = something() except ValueError use default_value
Even if we don't agree on a resolution for 3.5, I think there's more than enough interest for it to be worth someone's while to collate some of the proposals in a PEP - if nothing else, it will save rehashing the whole discussion next time it comes up :)
The benefits of this:
- the status quo is that various APIs are growing "default" parameters
to handle the case where they would otherwise throw an exception
- this is creating inconsistencies, as some such functions can be used
easily as expressions without risking any exception (those where such a parameter has been added), as well as a temptation to use "Look Before You Leap" pre-checks, even in cases where exception handling would be a better choice
- sequence indexing is a case where there is no current standard
mechanism for providing a default value, so you're either use a pre-check for the system length, or else using a full try statement or context manager to handle the IndexError
- by providing a clean, expression level syntax for handling a single
except clause and providing an alternate value for the expression, this problem could be solved once and for all in a systematic way, rather than needing to incrementally change the API of a variety of functions (as well as addressing the container subscripting case in a way that doesn't require switching away from using the subscript syntax to a normal function call, or switching from use an expression to a statement)
Some of the specific syntactic proposals:
x = op() except default if Exception x = op() except default for Exception x = op() except default from Exception x = op() except Exception return default x = op() except exc.attr if Exception as exc x = op() except exc.attr for Exception as exc x = op() except exc.attr from Exception as exc x = op() except Exception as exc return exc.attr
The except/if construct has parser ambiguity issues. While they're potentially solvable by requiring parens around conditional expressions in that context, that would come at the cost of a significant redesign of the language grammar.
The except/for option reads quite nicely, but introduces a substantially different meaning for "for".
The except/from option reads OK when combined with "as" and actually using the caught exception, but otherwise reads strangely.
The except/return option looks like it should either introduce a new scope or else return from the current function. The presence of the "as exc" clause in all variants actually suggests a new scope could be a good idea, given past experience with iteration variables in list comprehensions.
So, if we take the point of view that the new syntax is almost *literally* a shorthand for:
def _helper(op, exc, make_default): try: return op() except exc: return make_default() x = _helper(op, Exception, make_default)
Then that would suggest the following syntax and interpretation:
op() except Exception pass def _work_or_return_None(): try: return op() except Exception: pass _work_or_return_None() x = op() except Exception return value def _work_or_return_default(): try: return op() except Exception: return value x = _work_or_return_default() x = op() except Exception as exc return exc.attr def _work_or_process_exception(): try: return op() except Exception as exc: return exc.attr x = _work_or_process_exception()
OK, with the "introduces a new scope" caveat, consider me a fan of MAL's syntax unless/until someone points out a potential flaw that I have missed.
A possible addition: allow "raise" in addition to "pass" and "return" (for exception transformation as an expression)
Cheers, Nick.
It certainly feels right for the order to be normal value, exception, default value. So the syntax I would like is x = entries[0] except IndexError XXX None where XXX is some keyword. Ideally 'then' or perhaps 'when' which read better than 'else', but I understand adding a new keyword is a big deal. (FWIW I also wish trinary expressions were written as if condition then value-if-true else value-if-false which to me reads better than the status quo, but that ship has sailed.) Rob Cliffe
On 02/13/2014 02:26 PM, Rob Cliffe wrote:
It certainly feels right for the order to be normal value, exception, default value. So the syntax I would like is x = entries[0] except IndexError XXX None where XXX is some keyword. Ideally 'then' or perhaps 'when' which read better than 'else', but I understand adding a new keyword is a big deal. (FWIW I also wish trinary expressions were written as if condition then value-if-true else value-if-false which to me reads better than the status quo, but that ship has sailed.) Rob Cliffe
What about: x = entries[0] except IndexError then None
The weird point with: x = entries[0] except IndexError else None
is that 'else' seems to introduce a kind of double negation, where the first negation is due to 'except'. It this seems to indicate what _not_ to do in case of exception, which indeed makes no sense. 'else instead is ok in reverse order: x = entries[0] else None if IndexError
However, 'then' in python normally introduces an action, meaning a statement or block (and I'm firmly opposed to giving unrelated meanings to keywords or signs). But in such a case, an expression context, the action is just to choose and pick a value (here for the assignment), thus finally I don't find it that bad.
d
On Feb 13, 2014, at 10:45, spir denis.spir@gmail.com wrote:
On 02/13/2014 02:26 PM, Rob Cliffe wrote:
It certainly feels right for the order to be normal value, exception, default value. So the syntax I would like is x = entries[0] except IndexError XXX None where XXX is some keyword. Ideally 'then' or perhaps 'when' which read better than 'else', but I understand adding a new keyword is a big deal. (FWIW I also wish trinary expressions were written as if condition then value-if-true else value-if-false which to me reads better than the status quo, but that ship has sailed.) Rob Cliffe
What about: x = entries[0] except IndexError then None
The weird point with: x = entries[0] except IndexError else None
is that 'else' seems to introduce a kind of double negation, where the first negation is due to 'except'. It this seems to indicate what _not_ to do in case of exception, which indeed makes no sense. 'else instead is ok in reverse order: x = entries[0] else None if IndexError
However, 'then' in python normally introduces an action,
'then' in Python normally means nothing.
If Python _had_ a then keyword in it's if statement, there would have been a much more obvious syntax for the if expression, but it doesn't.
meaning a statement or block (and I'm firmly opposed to giving unrelated meanings to keywords or signs). But in such a case, an expression context, the action is just to choose and pick a value (here for the assignment), thus finally I don't find it that bad.
d
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Am 13.02.2014 13:50, schrieb Nick Coghlan:
On 13 February 2014 20:10, M.-A. Lemburg mal@egenix.com wrote:
On 13.02.2014 10:24, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
Here's a variant the resembles the code you'd write in a helper function to achieve the same thing, only stripped down somewhat:
x = something() except ValueError return default_value
def try_something(): try: return something() except ValueError: return default_value
x = something() except ValueError as exc return exc.message
def try_something(): try: return something() except ValueError as exc return exc.message
Obviously, having a keyword "use" would make a better fit :-)
x = something() except ValueError use default_value
Even if we don't agree on a resolution for 3.5, I think there's more than enough interest for it to be worth someone's while to collate some of the proposals in a PEP - if nothing else, it will save rehashing the whole discussion next time it comes up :)
The benefits of this:
- the status quo is that various APIs are growing "default" parameters
to handle the case where they would otherwise throw an exception
- this is creating inconsistencies, as some such functions can be used
easily as expressions without risking any exception (those where such a parameter has been added), as well as a temptation to use "Look Before You Leap" pre-checks, even in cases where exception handling would be a better choice
- sequence indexing is a case where there is no current standard
mechanism for providing a default value, so you're either use a pre-check for the system length, or else using a full try statement or context manager to handle the IndexError
- by providing a clean, expression level syntax for handling a single
except clause and providing an alternate value for the expression, this problem could be solved once and for all in a systematic way, rather than needing to incrementally change the API of a variety of functions (as well as addressing the container subscripting case in a way that doesn't require switching away from using the subscript syntax to a normal function call, or switching from use an expression to a statement)
Some of the specific syntactic proposals:
x = op() except default if Exception x = op() except default for Exception x = op() except default from Exception x = op() except Exception return default x = op() except exc.attr if Exception as exc x = op() except exc.attr for Exception as exc x = op() except exc.attr from Exception as exc x = op() except Exception as exc return exc.attr
For me any proposal that doesn't pair "except" with the exception class(es) like the statement version does would be right out.
It will be hard enough to remember the order of the expression, but "in the try-except block except gets the exception class, in the except expression it gets the default value and the exception class is specified with FOO" is silly.
Georg
On 2014-02-13 12:50, Nick Coghlan wrote:
On 13 February 2014 20:10, M.-A. Lemburg mal@egenix.com wrote:
On 13.02.2014 10:24, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
Here's a variant the resembles the code you'd write in a helper function to achieve the same thing, only stripped down somewhat:
x = something() except ValueError return default_value
def try_something(): try: return something() except ValueError: return default_value
x = something() except ValueError as exc return exc.message
def try_something(): try: return something() except ValueError as exc return exc.message
Obviously, having a keyword "use" would make a better fit :-)
x = something() except ValueError use default_value
Even if we don't agree on a resolution for 3.5, I think there's more than enough interest for it to be worth someone's while to collate some of the proposals in a PEP - if nothing else, it will save rehashing the whole discussion next time it comes up :)
The benefits of this:
- the status quo is that various APIs are growing "default" parameters
to handle the case where they would otherwise throw an exception
- this is creating inconsistencies, as some such functions can be used
easily as expressions without risking any exception (those where such a parameter has been added), as well as a temptation to use "Look Before You Leap" pre-checks, even in cases where exception handling would be a better choice
- sequence indexing is a case where there is no current standard
mechanism for providing a default value, so you're either use a pre-check for the system length, or else using a full try statement or context manager to handle the IndexError
- by providing a clean, expression level syntax for handling a single
except clause and providing an alternate value for the expression, this problem could be solved once and for all in a systematic way, rather than needing to incrementally change the API of a variety of functions (as well as addressing the container subscripting case in a way that doesn't require switching away from using the subscript syntax to a normal function call, or switching from use an expression to a statement)
Some of the specific syntactic proposals:
x = op() except default if Exception x = op() except default for Exception x = op() except default from Exception x = op() except Exception return default x = op() except exc.attr if Exception as exc x = op() except exc.attr for Exception as exc x = op() except exc.attr from Exception as exc x = op() except Exception as exc return exc.attr
You left one out:
x = op() except Exception: default
13.02.2014 13:50, Nick Coghlan wrote:
Some of the specific syntactic proposals:
x = op() except default if Exception x = op() except default for Exception x = op() except default from Exception x = op() except Exception return default x = op() except exc.attr if Exception as exc x = op() except exc.attr for Exception as exc x = op() except exc.attr from Exception as exc x = op() except Exception as exc return exc.attr
Please append also my proposal from another branch of this thread:
Simple Variant:
get_it() except (IndexError: None)
Variant with 'as':
get_it() except (OSError as exc: exc.errno)
Variant with multiple exceptions:
get_it() except (FileNotFound: 0, OSError as exc: exc.errno, Exception: None)
Cheers. *j
On 15 February 2014 07:29, Jan Kaliszewski zuo@chopin.edu.pl wrote:
13.02.2014 13:50, Nick Coghlan wrote:
Some of the specific syntactic proposals:
x = op() except default if Exception x = op() except default for Exception x = op() except default from Exception x = op() except Exception return default x = op() except exc.attr if Exception as exc x = op() except exc.attr for Exception as exc x = op() except exc.attr from Exception as exc x = op() except Exception as exc return exc.attr
Please append also my proposal from another branch of this thread
There's currently no volunteer to write a PEP - my post was just an illustration of the kinds of things such a PEP would need to consider. Until there is such a volunteer, the many syntax variants will remain scattered throughout the thread, and we'll likely end up rehashing the discussion in a couple of years time.
Note that writing a PEP isn't that complicated - the general process is covered in PEP 1, and the PEP editors take care of most of the details of checking that the markup is correct and then actually posting it on python.org (by committing it to the PEPs repo).
So, if anyone would like to write up this discussion, it will involve a few things:
1. Having enough time to follow the discussion and update it with new variants and any new open questions that come up 2. Being willing to read old PEPs for similar ideas (notably PEP 308 which added conditional expressions, but also those for yield from, the with statement, etc), and use those as a guide to the kind of things that need to be accounted for in a new syntax proposal 3. Being able to write up the proposal in such a way that it presents a clear rationale (based on the thread - in particular noting the uneven impact the lack of such a feature is having on function API designs), a clear *semantic* proposal (what does the new construct actually do, what is the scope of any name bindings involved, what scope do subexpressions evaluate in, etc), and then a presentation of the (many) syntactic proposals.
Cheers, Nick.
On Sat, Feb 15, 2014 at 11:46 AM, Nick Coghlan ncoghlan@gmail.com wrote:
There's currently no volunteer to write a PEP - my post was just an illustration of the kinds of things such a PEP would need to consider. Until there is such a volunteer, the many syntax variants will remain scattered throughout the thread, and we'll likely end up rehashing the discussion in a couple of years time.
I'll do up a PEP. If nothing else, it can be rejected and thus retain on record what's been said here.
It'll be my first PEP. Let's see how bad a job I make of it :)
ChrisA
On 2/13/2014 5:10 AM, M.-A. Lemburg wrote:
On 13.02.2014 10:24, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
Here's a variant the resembles the code you'd write in a helper function to achieve the same thing, only stripped down somewhat:
x = something() except ValueError return default_value
def try_something(): try: return something() except ValueError: return default_value
x = something() except ValueError as exc return exc.message
In Python 3 that would be x = something() except ValueError as exc return exc.args[0]
def try_something(): try: return something() except ValueError as exc return exc.message
Obviously, having a keyword "use" would make a better fit :-)
x = something() except ValueError use default_value
x = something() except ValueError as exc try exc.message
Try otherthing instead of something. That could even be chained
x = something() except ValueError as exc continue with exc.message
Instead of breaking due to the exception; 'with' could be left out.
x = something() except ValueError as exc pass exc.message
Pass expression back to x or consumer of expression.
On 02/13/2014 10:24 AM, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
I think the right way is not to call the function at all, but to check it. Conceptually:
if col.is_empty(): handle_special_case() else: handle_standard_case()
One use case, for example, is handing IndexError when retrieving an item from a sequence (which currently has no nice standard spelling, and isn't amenable to the "pass in a default answer" solution because it isn't a normal function call).
Another case not handled well by the status quo is when the default answer is expensive to calculate for some reason, so you really only want to calculate it if you actually need it.
The above schema applies to all cases where the failure (for the called service to perform its task) is _predictable_ by the client. Exceptions, in my view, are only for cases where the failure is impredictable (see below) *and* nevertheless belongs to to the application semantics; meaning, it does not result from a programming error (eg the collection should _not_ be empty), else one should not use exception catching, but instead let an error message helpfully inform us about the logical error.
Typical cases of impredictability on the client side are search/find functions, and dealing with the outer world, in particular the file system. In addition, in such cases using for instance a 'has' function (or in python 'in') to first check would do the job (of searching) twice. This is why, probably, there are alternative funcs like python's 'get' for dicts. Maybe this scheme could be generalised: not only eg list.get(i, default), but all cases of potentially failing funcs that return a result.
For functions (actions, in fact) that instead perform an effect, cases are more diverse and not always in our hands. For instance, one may think at a func which would create file if not existant, instead of replacing its contents (or appending to it): but this would I guess require the filesystem to provide such a facility. As far as I know, we are left with having routines search twice (for the abstract file location in the dir tree).
d
spir denis.spir@gmail.com writes:
I think the right way is not to call the function at all, but to check it. Conceptually:
if col.is_empty(): handle_special_case() else: handle_standard_case()
Or, better from two perspectives (“empty” should normally entail “evaluates to boolean false”; and, the normal case should be the first branch from the “if”)::
if col: handle_standard_case() else: handle_empty_case()
On 02/13/2014 12:10 PM, Ben Finney wrote:
spir denis.spir@gmail.com writes:
I think the right way is not to call the function at all, but to check it. Conceptually:
if col.is_empty(): handle_special_case() else: handle_standard_case()
Or, better from two perspectives (“empty” should normally entail “evaluates to boolean false”; and, the normal case should be the first branch from the “if”)::
if col: handle_standard_case() else: handle_empty_case()
You are right (I always forget that empty means false in python, in a logical context, which i don't find obvious at all --I wonder if any other lang follows this choice).
d
On Thu, Feb 13, 2014 at 10:10:16PM +1100, Ben Finney wrote:
spir denis.spir@gmail.com writes:
I think the right way is not to call the function at all, but to check it. Conceptually:
if col.is_empty(): handle_special_case() else: handle_standard_case()
Or, better from two perspectives (“empty” should normally entail “evaluates to boolean false”; and, the normal case should be the first branch from the “if”)::
if col: handle_standard_case() else: handle_empty_case()
I'm afraid that entails a race-condition. col may be non-empty at the moment you check it, but by the time you handle the non-empty case a microsecond later it has become empty.
This is why we have both Look Before You Leap and Easier To Ask Forgiveness Than Permission. We can perform LBYL in an expression using ternary if operator:
(handle_standard_case if col else handle_empty_case)()
but there's no equivalent to a try...except in a single expression, which is what this thread is about.
On 2/13/2014 4:24 AM, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
Leaving aside syntax, the idea makes sense to me as follows:
In Python, 'a or b' is not a just a logic operator but is generalized to mean, for instance, the following: 'a, unless a is falsey, in which case, b instead. The proposal is to introduce a syntax that means the same with 'unless a is falsey' replaced by 'unless a raises an exception of class C'. The class 'C' make 'a new_op b' inadequate.
The two alternations are related when exception C results from an input that is at least conceptually falsey. Some such cases can be handled by 'or' (see below). Iterators, however, are seen as truthy even when empty (conceptually falsey). And that cannot be fixed since some iterators cannot know if they are empty until __next__ is called and bool is not supposed to raise.
One use case, for example, is handing IndexError when retrieving an item from a sequence (which currently has no nice standard spelling, and isn't amenable to the "pass in a default answer" solution because it isn't a normal function call).
This is easy, if not obvious
(seq or ['default'])[0]
'default'
(Python's precedence rules make the parentheses optional).
Doing something similar instead of dict.get is messier but conceptually the same.
{}.get('key', 'default')
'default'
{} or {'key':'default'}['key']
'default'
The general scheme is f(a or b, *args), where b is the input to f that gives the default as the output. Sort of inverse(partial(f, args))(default).
This does not work if the exception-raising inputs are not all seen as falsey (as with empty iterables) or if one cannot invert the partial function to get b. In either case, we have to input a and replace exceptions with b.
Am 13.02.2014 16:28, schrieb Terry Reedy:
On 2/13/2014 4:24 AM, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
Leaving aside syntax, the idea makes sense to me as follows:
In Python, 'a or b' is not a just a logic operator but is generalized to mean, for instance, the following: 'a, unless a is falsey, in which case, b instead. The proposal is to introduce a syntax that means the same with 'unless a is falsey' replaced by 'unless a raises an exception of class C'. The class 'C' make 'a new_op b' inadequate.
The two alternations are related when exception C results from an input that is at least conceptually falsey. Some such cases can be handled by 'or' (see below). Iterators, however, are seen as truthy even when empty (conceptually falsey). And that cannot be fixed since some iterators cannot know if they are empty until __next__ is called and bool is not supposed to raise.
One use case, for example, is handing IndexError when retrieving an item from a sequence (which currently has no nice standard spelling, and isn't amenable to the "pass in a default answer" solution because it isn't a normal function call).
This is easy, if not obvious
(seq or ['default'])[0]
'default'
(Python's precedence rules make the parentheses optional).
Not sure if I understand correctly, but in the example above they are not.
Doing something similar instead of dict.get is messier but conceptually the same.
{}.get('key', 'default')
'default'
{} or {'key':'default'}['key']
'default'
And this also quite certainly does not do what you intended.
Georg
On 2/13/2014 10:28 AM, Terry Reedy wrote:
On 2/13/2014 4:24 AM, Nick Coghlan wrote:
General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the proliferation of relatively ad hoc alternative solutions (in particular, the popularity of being able to pass in default values to handle empty iterables).
Leaving aside syntax, the idea makes sense to me as follows:
In Python, 'a or b' is not a just a logic operator but is generalized to mean, for instance, the following: 'a, unless a is falsey, in which case, b instead. The proposal is to introduce a syntax that means the same with 'unless a is falsey' replaced by 'unless a raises an exception of class C'. The class 'C' make 'a new_op b' inadequate.
The two alternations are related when exception C results from an input that is at least conceptually falsey. Some such cases can be handled by 'or' (see below). Iterators, however, are seen as truthy even when empty (conceptually falsey). And that cannot be fixed since some iterators cannot know if they are empty until __next__ is called and bool is not supposed to raise.
One use case, for example, is handing IndexError when retrieving an item from a sequence (which currently has no nice standard spelling, and isn't amenable to the "pass in a default answer" solution because it isn't a normal function call).
This is easy, if not obvious
To be fair, this is only easy for the index 0 case, which is however, common.
(seq or ['default'])[0]
'default'
(Python's precedence rules make the parentheses optional).
Doing something similar instead of dict.get is messier but conceptually the same.
{}.get('key', 'default')
'default'
{} or {'key':'default'}['key']
'default'
However, this is a rare case.
The general scheme is f(a or b, *args), where b is the input to f that gives the default as the output. Sort of inverse(partial(f, args))(default).
This does not work if the exception-raising inputs are not all seen as falsey (as with empty iterables)
or with non-empty lists whose length is less than the index+1 (though this latter example can be 'fixed' by using a conditional expression).
or if one cannot invert the partial function to get b.
Or if it is unboundedly expensive.
(lis if len(lis) > n else (n+1)*['default'])[n] # () needed
'default'
Doing something similar for a non-empty dict that might be missing a key is at least as messy.
In either case, we should or must input a and replace exceptions with b.
Terry Reedy wrote:
(seq or ['default'])[0]
'default'
(Python's precedence rules make the parentheses optional).
Um, no, they don't:
seq = ['a'] (seq or ['default'])[0]
'a'
seq or ['default'][0]
['a']
In any case, this only works for an extremely special case (an index of 0), and can hardly be seen as an alternative to the proposed except-expression.
{}.get('key', 'default')
'default'
{} or {'key':'default'}['key']
'default'
This is an even *more* special case, since it only works if you know that the only way the dict can possibly fail to contain the key is if it's completely empty.
Nick Coghlan wrote:
"if" would be better, but, as you already noted, poses significant parsing challenges (since it likely wouldn't be easy to require that ternary expressions use parentheses in this new construct, but still allow the parentheses to be omitted in the general case).
I don't think that would be an insurmountable difficulty. The if-expression appears at the 'test' level in the grammar, so all it should take is for the expression following 'except' to be something lower, such as an 'or_test'.
There are precedents for this kind of thing, e.g. yield expressions can appear unparenthesised in some contexts but not others.
On Thu, Feb 13, 2014 at 2:20 PM, Amber Yust amber.yust@gmail.com wrote:
Ah, that's a good point (the two-directionality of yield had slipped my mind). I had considered suggesting return instead of yield, which wouldn't have that problem, but it felt like return would be more confusing to see in a context where it doesn't actually return from the enclosing scope.
Yeah. I like the parallel with "get this unless it's empty in which case use this default", but the 'or' keyword is already a bit awkward there, so I don't want to advocate that.
name = input("Name [Anonymous]: ") or "Anonymous" phone = addressbook[name] except KeyError or "Unknown"
It's a nice parallel, but doesn't read well (plus, >> KeyError or "Unknown" << is already an expression).
+1 on the feature but it definitely needs a syntax that makes sense. Of course, it could be done as a function call:
def catch(callme, catchme, returnme): try: return callme() except catchme: return returnme
phone = catch(lambda: addressbook[name], KeyError, "Unknown")
but that's *really* clunky.
ChrisA
On 02/13/2014 04:38 AM, Chris Angelico wrote:
On Thu, Feb 13, 2014 at 2:20 PM, Amber Yust amber.yust@gmail.com wrote:
Ah, that's a good point (the two-directionality of yield had slipped my mind). I had considered suggesting return instead of yield, which wouldn't have that problem, but it felt like return would be more confusing to see in a context where it doesn't actually return from the enclosing scope.
Yeah. I like the parallel with "get this unless it's empty in which case use this default", but the 'or' keyword is already a bit awkward there, so I don't want to advocate that.
name = input("Name [Anonymous]: ") or "Anonymous" phone = addressbook[name] except KeyError or "Unknown"
It's a nice parallel, but doesn't read well (plus, >> KeyError or "Unknown" << is already an expression).
+1 on the feature but it definitely needs a syntax that makes sense.
I don't see any issue with: phone = addressbook[name] except "Unknown" if KeyError phone = addressbook[name] except "Unknown" if KeyError as e It is similar in syntax and meaning with if-expressions, with the first keyword 'except' obviously making all the difference we need.
[By the way, this shows that: x = b if cond else a should really be: x = a else b if cond The difference being that the standard case is expressed first, the exceptional one being then introduced as an special variant.]
In some languages (eg Lua) there is no difference between absent values (vars, attributes, items...) because of an arror (they should be there, in python we get an exception) and optional values (in python there is None for this meaning). In the latter case, presence of absence both are valid alternatives in the app's logic. Such a lack of distinction (in Lua, both yield nil) is very bad, indeed, but combined with "permissive" logical expressions (in which operands may not be logical, and result value as well) allows: phone = addressbook[name] or "Unknown"
However, this idiom is mainly common in lua because there are no standard param values (defaults): Shape.fill = function (shape, color) color = color or black ... end This also shows that the cases in python were we do need such an idiom are pretty rare, all in all: should we bother?
Of course, it could be done as a function call:
def catch(callme, catchme, returnme): try: return callme() except catchme: return returnme
phone = catch(lambda: addressbook[name], KeyError, "Unknown")
but that's *really* clunky.
I'd like such a solution if builtin (for it to be standard, thus widely shared in python code) and did not force writing a lambda.
d
On 13 February 2014 11:25, spir denis.spir@gmail.com wrote:
I don't see any issue with: phone = addressbook[name] except "Unknown" if KeyError phone = addressbook[name] except "Unknown" if KeyError as e It is similar in syntax and meaning with if-expressions, with the first keyword 'except' obviously making all the difference we need.
What I dislike about this variant is that in a normal try statement, the exception goes after the "except" keyword. Here, it's the alternative value that goes there. I find that very easy to misread.
Personally, I quite like "EXPR except EXCEPTIONTYPE return VALUE" if we have to stick with existing keywords. If I had free rein I might go for a new keyword "then" instead of "return".
Paul
On Thu, Feb 13, 2014 at 10:36 PM, Paul Moore p.f.moore@gmail.com wrote:
Personally, I quite like "EXPR except EXCEPTIONTYPE return VALUE" if we have to stick with existing keywords. If I had free rein I might go for a new keyword "then" instead of "return".
Hmm. Stupid idea:
EXPR expr EXCEPTIONTYPE pass VALUE
Passing something is kinda like returning it, right? *ducks the rotten tomatoes*
ChrisA
On Thu, Feb 13, 2014 at 1:50 PM, Chris Angelico rosuav@gmail.com wrote:
On Thu, Feb 13, 2014 at 10:36 PM, Paul Moore p.f.moore@gmail.com wrote:
Personally, I quite like "EXPR except EXCEPTIONTYPE return VALUE" if we have to stick with existing keywords. If I had free rein I might go for a new keyword "then" instead of "return".
Hmm. Stupid idea:
EXPR expr EXCEPTIONTYPE pass VALUE
Passing something is kinda like returning it, right? *ducks the rotten tomatoes*
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
--
You received this message because you are subscribed to a topic in the Google Groups "python-ideas" group. To unsubscribe from this topic, visit https://groups.google.com/d/topic/python-ideas/ZoBGdwuH3uk/unsubscribe. To unsubscribe from this group and all its topics, send an email to python-ideas+unsubscribe@googlegroups.com. For more options, visit https://groups.google.com/groups/opt_out.
On Thu, Feb 13, 2014 at 10:54 PM, Ram Rachum ram@rachum.com wrote:
On Thu, Feb 13, 2014 at 1:50 PM, Chris Angelico rosuav@gmail.com wrote:
On Thu, Feb 13, 2014 at 10:36 PM, Paul Moore p.f.moore@gmail.com wrote:
Personally, I quite like "EXPR except EXCEPTIONTYPE return VALUE" if we have to stick with existing keywords. If I had free rein I might go for a new keyword "then" instead of "return".
Hmm. Stupid idea:
EXPR expr EXCEPTIONTYPE pass VALUE
Passing something is kinda like returning it, right? *ducks the rotten tomatoes*
Do you mean my idea is win, or the notion of throwing rotten tomatoes at me is win? I'm inclined to the latter theory :)
Oh, and that should be "EXPR except EXCEPTIONTYPE", of course. I wasn't sure if I'd clicked Send or not, edited, and reclicked Send, so you may have a corrected copy as well as that one. But you knew already what it ought to have been.
ChrisA
On 02/13/2014 01:00 PM, Chris Angelico wrote:
On Thu, Feb 13, 2014 at 10:54 PM, Ram Rachum ram@rachum.com wrote:
On Thu, Feb 13, 2014 at 1:50 PM, Chris Angelico rosuav@gmail.com wrote:
On Thu, Feb 13, 2014 at 10:36 PM, Paul Moore p.f.moore@gmail.com wrote:
Personally, I quite like "EXPR except EXCEPTIONTYPE return VALUE" if we have to stick with existing keywords. If I had free rein I might go for a new keyword "then" instead of "return".
Hmm. Stupid idea:
EXPR expr EXCEPTIONTYPE pass VALUE
Passing something is kinda like returning it, right? *ducks the rotten tomatoes*
Do you mean my idea is win, or the notion of throwing rotten tomatoes at me is win? I'm inclined to the latter theory :)
Oh, and that should be "EXPR except EXCEPTIONTYPE", of course. I wasn't sure if I'd clicked Send or not, edited, and reclicked Send, so you may have a corrected copy as well as that one. But you knew already what it ought to have been.
Your proposal has the big advantage of stating things in (the right) order: normal_value, special_condition, special_value and it still lets the door open to naming the exception, using 'as', if later needed: EXPR except EXCEPTIONTYPE as EXCEPTIONNAME pass VALUE
d
On 02/13/2014 12:36 PM, Paul Moore wrote:
On 13 February 2014 11:25, spirdenis.spir@gmail.com wrote:
I don't see any issue with: phone = addressbook[name] except "Unknown" if KeyError phone = addressbook[name] except "Unknown" if KeyError as e It is similar in syntax and meaning with if-expressions, with the first keyword 'except' obviously making all the difference we need.
What I dislike about this variant is that in a normal try statement, the exception goes after the "except" keyword. Here, it's the alternative value that goes there. I find that very easy to misread.
You are right!
d
On Thu, Feb 13, 2014 at 10:25 PM, spir denis.spir@gmail.com wrote:
I don't see any issue with: phone = addressbook[name] except "Unknown" if KeyError phone = addressbook[name] except "Unknown" if KeyError as e It is similar in syntax and meaning with if-expressions, with the first keyword 'except' obviously making all the difference we need.
So the keyword 'if' would come up in two different expression contexts:
value if cond else othervalue
value except othervalue if cond
In each case, the condition (which in the latter is an exception type, while in the former it's a boolean) follows the word 'if', just as it does in the statement form. That's reasonably elegant. Problem is, that loses the elegance of matching the statement form of 'except', which has the exception type following the word 'except'.
I'd kinda like to see it worded as "if except", but then it needs something else to separate the exception(s) from the value. It could actually be done just like the if/else form, except (pun intended) that that overemphasizes the exceptional case:
phone = "Unknown" if except KeyError else addressbook[name]
In terms of argument order, I'm looking for:
phone = addressbook[name] unless except KeyError as e then "Unknown"
but neither 'unless' nor 'then' is currently a keyword.
[By the way, this shows that: x = b if cond else a should really be: x = a else b if cond The difference being that the standard case is expressed first, the exceptional one being then introduced as an special variant.]
My example tends to agree with you... but my example is using "if" to introduce the abnormal case, whereas it's common to spell a block if the other way:
if normal-case: code code code else: abnormal code
C's ternary operator puts the expressions in the order "condition, if_true, if_false". Python's puts them "if_true, condition, if_false". You're proposing "if_false, if_true, condition". We're half way to covering all the permutations!
ChrisA
On 02/13/2014 12:48 PM, Chris Angelico wrote: On Thu, Feb 13, 2014 at 10:25 PM, spir denis.spir@gmail.com wrote:
[By the way, this shows that: x = b if cond else a should really be: x = a else b if cond The difference being that the standard case is expressed first, the exceptional one being then introduced as an special variant.]
My example tends to agree with you... but my example is using "if" to introduce the abnormal case, whereas it's common to spell a block if the other way:
if normal-case: code code code else: abnormal code
I rather write code the the other way round, with the condition determinig the special case. This also matches the common idom: if special-case: deal-with-it return [result] deal-with-normal-case # no else needed
Generally, I think logical vars should be 'false' by default, expressing the standard/rest/off state, with 'true' meaning something new or activated ('on'), or otherwise special.
C's ternary operator puts the expressions in the order "condition, if_true, if_false". Python's puts them "if_true, condition, if_false". You're proposing "if_false, if_true, condition".
That's because I think (1) the standard value should come first (2) the condition should express the special case instead.
We're half way to covering all the permutations!
;-)
Actually, the ideal order for me would be: if_false, condition, if_true in the sense of normal_value, special_condition, special_value but this does not match the meaning of natural language preposition (here, 'if').
d
On Fri, Feb 14, 2014 at 9:22 AM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
Chris Angelico wrote:
phone = "Unknown" if except KeyError else addressbook[name]
-42. My brain gets totally derailed when it hits "if except"; it parses that bit as a syntax error.
Yeah, and it reads backwards anyway. I don't particularly like that syntax.
ChrisA
Am 13.02.2014 12:25, schrieb spir:
[By the way, this shows that: x = b if cond else a should really be: x = a else b if cond The difference being that the standard case is expressed first, the exceptional one being then introduced as an special variant.]
No it shouldn't -- your syntax associates "b" with both the else ("else b") and the "if" clause ("b if cond"), which makes me think "which of them will it be".
Also, there's nothing intrinsically more "exceptional" about the "if" branch than the "else" branch; this entirely dependent on the expression.
Georg
On Thu, Feb 13, 2014 at 3:25 AM, spir denis.spir@gmail.com wrote:
Of course, it could be done as a function call:
def catch(callme, catchme, returnme): try: return callme() except catchme: return returnme
phone = catch(lambda: addressbook[name], KeyError, "Unknown") but that's *really* clunky.
I don't really find this function all the clunky. However, it also does not solve the case of an expensive 'except' clause that Nick pointed out as desirable to avoid evaluating (or, for that matter, an except clause with side effects).
E.g.
phone = catch(lambda: addressbook[name], KeyError, lookup_from_internet(name))
Actually. What if we just reused 'try'?
foo = bar() except BazException try 'qux'
This also leads naturally to chaining multiple possible fallbacks:
foo = bar() except BarException try baz() except BazException try None
On Thu Feb 13 2014 at 10:32:16 AM, David Mertz mertz@gnosis.cx wrote:
On Thu, Feb 13, 2014 at 3:25 AM, spir denis.spir@gmail.com wrote:
Of course, it could be done as a function call:
def catch(callme, catchme, returnme): try: return callme() except catchme: return returnme
phone = catch(lambda: addressbook[name], KeyError, "Unknown") but that's *really* clunky.
I don't really find this function all the clunky. However, it also does not solve the case of an expensive 'except' clause that Nick pointed out as desirable to avoid evaluating (or, for that matter, an except clause with side effects).
E.g.
phone = catch(lambda: addressbook[name], KeyError, lookup_from_internet(name))
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is
to the 21st century what the slave trade was to the 16th. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 02/13/2014 07:43 PM, Amber Yust wrote:
Actually. What if we just reused 'try'?
foo = bar() except BazException try 'qux'
This also leads naturally to chaining multiple possible fallbacks:
foo = bar() except BarException try baz() except BazException try None
I like it. Especially because 'try' already works with 'except'. (But note that 'try', like my proposal of 'then', normally introduces a block).
d
mainly, a colon introduces a block, which is why i don’t like the colon variant of this expression.
2014-02-13 19:47 GMT+01:00 spir denis.spir@gmail.com:
On 02/13/2014 07:43 PM, Amber Yust wrote:
Actually. What if we just reused 'try'?
foo = bar() except BazException try 'qux'
This also leads naturally to chaining multiple possible fallbacks:
foo = bar() except BarException try baz() except BazException try
None
I like it. Especially because 'try' already works with 'except'. (But note that 'try', like my proposal of 'then', normally introduces a block).
d _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Thu, Feb 13, 2014 at 1:47 PM, spir denis.spir@gmail.com wrote:
On 02/13/2014 07:43 PM, Amber Yust wrote:
Actually. What if we just reused 'try'?
foo = bar() except BazException try 'qux'
This also leads naturally to chaining multiple possible fallbacks:
foo = bar() except BarException try baz() except BazException try
None
I like it. Especially because 'try' already works with 'except'. (But note that 'try', like my proposal of 'then', normally introduces a block).
This strikes me as counterintuitive because it is inconsistent: 'bar()' is being tried, but does not follow 'try', while the others do. And then the 'try None' has no corresponding 'except'.
Suggestion: an expression like
foo = (try bar() except BarException)
that defaults to None if the exception is caught. This could then be chained with 'or':
foo = (try bar() except BarException) or (try baz() except BazException)
as distinguished from
foo = (try bar() except BarException) or baz()
which does not do any exception handling for baz().
(Apologies if something like this has been proposed above; I could not find it from skimming the thread.)
Nathan
d _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Feb 13, 2014, at 11:04, Nathan Schneider nathan@cmu.edu wrote:
On Thu, Feb 13, 2014 at 1:47 PM, spir denis.spir@gmail.com wrote:
On 02/13/2014 07:43 PM, Amber Yust wrote:
Actually. What if we just reused 'try'?
foo = bar() except BazException try 'qux'
This also leads naturally to chaining multiple possible fallbacks:
foo = bar() except BarException try baz() except BazException try None
I like it. Especially because 'try' already works with 'except'. (But note that 'try', like my proposal of 'then', normally introduces a block).
This strikes me as counterintuitive because it is inconsistent: 'bar()' is being tried, but does not follow 'try', while the others do. And then the 'try None' has no corresponding 'except'.
Suggestion: an expression like
foo = (try bar() except BarException)
that defaults to None if the exception is caught. This could then be chained with 'or':
foo = (try bar() except BarException) or (try baz() except BazException)
But what if bar() can successfully return None, or just a falsey value in general?
Note that this is exactly the reason we needed the if expression: because or is tempting but incorrect in such cases.
as distinguished from
foo = (try bar() except BarException) or baz()
which does not do any exception handling for baz().
(Apologies if something like this has been proposed above; I could not find it from skimming the thread.)
Nathan
d _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Thu, Feb 13, 2014 at 12:43 PM, Amber Yust amber.yust@gmail.com wrote:
Actually. What if we just reused 'try'?
foo = bar() except BazException try 'qux'
This also leads naturally to chaining multiple possible fallbacks:
foo = bar() except BarException try baz() except BazException try None
This is my favorite so far, followed by s/try/return/. I agree with Nathan Schneider though, it does seem odd to have "try" following "except" rather than the other way around.
What about just allowing simple try...except constructs to be condensed to a single line?
foo = try bar() except BarException as e e.args[0]
The last part of that ("as e e.args[0]") is a pain to read due to no clear separation, just whitespace.
On Thu Feb 13 2014 at 2:00:00 PM, Zachary Ware < zachary.ware+pyideas@gmail.com> wrote:
On Thu, Feb 13, 2014 at 12:43 PM, Amber Yust amber.yust@gmail.com wrote:
Actually. What if we just reused 'try'?
foo = bar() except BazException try 'qux'
This also leads naturally to chaining multiple possible fallbacks:
foo = bar() except BarException try baz() except BazException try
None
This is my favorite so far, followed by s/try/return/. I agree with Nathan Schneider though, it does seem odd to have "try" following "except" rather than the other way around.
What about just allowing simple try...except constructs to be condensed to a single line?
foo = try bar() except BarException as e e.args[0]
-- Zach _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Thu, Feb 13, 2014 at 4:02 PM, Amber Yust amber.yust@gmail.com wrote: <cut>
On Thu Feb 13 2014 at 2:00:00 PM, Zachary Ware zachary.ware+pyideas@gmail.com wrote:
What about just allowing simple try...except constructs to be condensed to a single line?
foo = try bar() except BarException as e e.args[0]
<paste>
The last part of that ("as e e.args[0]") is a pain to read due to no clear separation, just whitespace.
I don't disagree, but it's less bad than some of the other alternatives :). It's also not quite as bad when not using the exception (which I suspect would be somewhat more common):
foo = try bar() except BarException "default"
On Fri, Feb 14, 2014 at 9:11 AM, Zachary Ware zachary.ware+pyideas@gmail.com wrote:
On Thu, Feb 13, 2014 at 4:02 PM, Amber Yust amber.yust@gmail.com wrote:
<cut> > On Thu Feb 13 2014 at 2:00:00 PM, Zachary Ware > <zachary.ware+pyideas@gmail.com> wrote: >> What about just allowing simple try...except constructs to be >> condensed to a single line? >> >> foo = try bar() except BarException as e e.args[0] <paste> > The last part of that ("as e e.args[0]") is a pain to read due to no clear > separation, just whitespace.
I don't disagree, but it's less bad than some of the other alternatives :). It's also not quite as bad when not using the exception (which I suspect would be somewhat more common):
foo = try bar() except BarException "default"
Looks like a bug magnet, if nothing else. Are you allowed newlines in that construct?
ChrisA
My 3 cents (not proposed yet, AFAIK):
1. Simple Variant:
get_it() except (IndexError: None)
2. Variant with 'as':
get_it() except (OSError as exc: exc.errno)
3. Variant with multiple exceptions:
get_it() except (FileNotFound: 0, OSError as exc: exc.errno, Exception: None)
Cheers. *j
I propose the syntax
x = try entries[0] except IndexError: None
Advantages:
(1) Similar to the current try ... except syntax, so meaning immediately obvious.
(2) Allows all the current (and future??) syntax of except clauses: x = try get_it() except IOError as e: e.errno x = try get_it() except (OSError, IOError): None x = try entries[0] except NameError: ProgramError[1] except IndexError: None
(3) Unambiguous: in the last example the second "except" traps an IndexError that occurs during the evaluation of "entries[0]", not during the evaluation of "ProgramError[1]" (the latter would be written with a second "try" before "ProgramError[1], not that I would recommend doing it). An "except" refers to the expression following the nearest preceding "try". I.e. there is no "dangling except" problem.
(4) Straightforward to parse. The leading "try" immediately tells the parser what to expect. And the "except"s and colons are unambiguous delimiters. Ditto for a human reader.
(5) No new keyword, or strained use of an existing keyword, needed.
It would be illegal to have a "try" expression without at least one "except", just as is it currently illegal to have a "try" statement without at least one "except" or "finally".
Rob Cliffe
On 13/02/2014 23:37, Greg Ewing wrote:
Amber Yust wrote:
Actually. What if we just reused 'try'?
foo = bar() except BazException try 'qux'
This suggests that trying 'qux' may somehow fail to work, which is not the intended meaning at all.
Here's another one:
things[i] (except IndexError: 42)
This has the advantage of putting the colon inside parens, where it's less likely to get confused with other uses of colons in the same line (by humans, if not by the computer).
Also it might be useful to be able to say
things.remove(i) (except ValueError: pass)
which would be equivalent to
things.remove(i) (except ValueError: None)
but would read more smoothly in cases where you're not interested in the value of the expression.
On Sat, Feb 15, 2014 at 11:20:13AM +1300, Greg Ewing wrote:
Here's another one:
things[i] (except IndexError: 42)
I believe Jan Kaliszewski independently came up with the same syntax earlier, which is a good sign. Two people suggesting the same thing is promising.
Jan also suggested that this syntax allows binding the exception:
things[i] (except IndexError as exc: exc.args)
and multiple except terms:
things[i] (except IndexError as exc: exc.args, except NameError: "missing", except KeyError: None, )
One might also catch multiple exceptions in a single term:
things[i] (except IndexError,KeyError as exc: exc.args)
It's a tiny bit unusual to have a colon that doesn't introduce a block, but only a tiny bit. There is precedent:
lambda x, y: (x+y)/(x*y)
This suggests that perhaps the parentheses aren't needed unless you have multiple except parts:
things[i] (except IndexError, KeyError as exc: exc.args) things[i] except IndexError, KeyError as exc: exc.args
but I think they ought to be compulsory if you use multiple excepts. And of course they are useful for continuing over multiple lines.
I think this syntax looks good when used in compound expressions:
mylist = [23, lambda x: x+1, things[i] except IndexError: 42, ""]
result = function(a, b except NameError: "undefined", c)
result = function(a, b, c) except ValueError: float('nan')
if something(x) except TypeError: None: block
Dicts are problematic. Should the dict colon bind more or less strongly than the except colon? I suggest we follow the same rule as for lambda:
adict = {lambda x: x+1: "value", key+1 except TypeError: 23: "value", }
Here is a torture-test for the syntax: can we combine it with an if-expression? I think we can, although you may need parentheses to disambiguate the expression:
something(x) (except TypeError: a if condition else b)
((something(x) if x else other(x)) (except ValueError: -1)
something(x) if x else (other(x) except ValueError: -1)
Trying to do too much in a single expression is never exactly *beautiful*, but it can be read.
This does look a tiny bit like a function call, especially if you delete the space between the leading expression and the opening bracket:
# ugly, don't do this things[i](except IndexError: 42)
but that's not actually ambiguous, since the keyword except cannot be an argument to a function.
I like this. I think this is the first non-sucky syntax I've seen (sorry to everyone who proposed sucky syntax *wink*).
This has the advantage of putting the colon inside parens, where it's less likely to get confused with other uses of colons in the same line (by humans, if not by the computer).
Also it might be useful to be able to say
things.remove(i) (except ValueError: pass)
which would be equivalent to
things.remove(i) (except ValueError: None)
but would read more smoothly in cases where you're not interested in the value of the expression.
Certainly not! pass implies that *no return result is generated at all*, which is not possible in Python. Returning None is the right thing to do.
hmm i still don’t like the colon. everywhere else in python, it means “new block follows”
and i’m sure nobody here wants to allow the following?
value = hovercraft() except EelsException: remove_eels() no_eels #this is assigned to “value” if we have too many eels
i like it in coffeescript and scala, but it’s not used in python, so we shouldn’t introduce it.
neither should we introduce a colon that *can’t* be followed by a block.
if we wanted the colon in any case, we’d need to do:
winner = germans_win() except EurekaException: return greeks
value = hovercraft() except EelsException: remove_eels() return no_eels
but i’m still partial to stomach_content = eat() except ThinMintError pass explode()
2014-02-15 19:11 GMT+01:00 Steven D'Aprano steve@pearwood.info:
On Sat, Feb 15, 2014 at 11:20:13AM +1300, Greg Ewing wrote:
Here's another one:
things[i] (except IndexError: 42)
I believe Jan Kaliszewski independently came up with the same syntax earlier, which is a good sign. Two people suggesting the same thing is promising.
Jan also suggested that this syntax allows binding the exception:
things[i] (except IndexError as exc: exc.args)
and multiple except terms:
things[i] (except IndexError as exc: exc.args, except NameError: "missing", except KeyError: None, )
One might also catch multiple exceptions in a single term:
things[i] (except IndexError,KeyError as exc: exc.args)
It's a tiny bit unusual to have a colon that doesn't introduce a block, but only a tiny bit. There is precedent:
lambda x, y: (x+y)/(x*y)
This suggests that perhaps the parentheses aren't needed unless you have multiple except parts:
things[i] (except IndexError, KeyError as exc: exc.args) things[i] except IndexError, KeyError as exc: exc.args
but I think they ought to be compulsory if you use multiple excepts. And of course they are useful for continuing over multiple lines.
I think this syntax looks good when used in compound expressions:
mylist = [23, lambda x: x+1, things[i] except IndexError: 42, ""] result = function(a, b except NameError: "undefined", c) result = function(a, b, c) except ValueError: float('nan') if something(x) except TypeError: None: block
Dicts are problematic. Should the dict colon bind more or less strongly than the except colon? I suggest we follow the same rule as for lambda:
adict = {lambda x: x+1: "value", key+1 except TypeError: 23: "value", }
Here is a torture-test for the syntax: can we combine it with an if-expression? I think we can, although you may need parentheses to disambiguate the expression:
something(x) (except TypeError: a if condition else b) ((something(x) if x else other(x)) (except ValueError: -1) something(x) if x else (other(x) except ValueError: -1)
Trying to do too much in a single expression is never exactly *beautiful*, but it can be read.
This does look a tiny bit like a function call, especially if you delete the space between the leading expression and the opening bracket:
# ugly, don't do this things[i](except IndexError: 42)
but that's not actually ambiguous, since the keyword except cannot be an argument to a function.
I like this. I think this is the first non-sucky syntax I've seen (sorry to everyone who proposed sucky syntax *wink*).
This has the advantage of putting the colon inside parens, where it's less likely to get confused with other uses of colons in the same line (by humans, if not by the computer).
Also it might be useful to be able to say
things.remove(i) (except ValueError: pass)
which would be equivalent to
things.remove(i) (except ValueError: None)
but would read more smoothly in cases where you're not interested in the value of the expression.
Certainly not! pass implies that *no return result is generated at all*, which is not possible in Python. Returning None is the right thing to do.
-- Steven _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Not everywhere. As previously mentioned, lambdas use a colon which is followed by an expression, not a block. On Feb 15, 2014 2:24 PM, "Philipp A." flying-sheep@web.de wrote:
hmm i still don’t like the colon. everywhere else in python, it means “new block follows”
and i’m sure nobody here wants to allow the following?
value = hovercraft() except EelsException: remove_eels() no_eels #this is assigned to “value” if we have too many eels
i like it in coffeescript and scala, but it’s not used in python, so we shouldn’t introduce it.
neither should we introduce a colon that *can’t* be followed by a block.
if we wanted the colon in any case, we’d need to do:
winner = germans_win() except EurekaException: return greeks
value = hovercraft() except EelsException: remove_eels() return no_eels
but i’m still partial to stomach_content = eat() except ThinMintError pass explode()
2014-02-15 19:11 GMT+01:00 Steven D'Aprano steve@pearwood.info:
On Sat, Feb 15, 2014 at 11:20:13AM +1300, Greg Ewing wrote:
Here's another one:
things[i] (except IndexError: 42)
I believe Jan Kaliszewski independently came up with the same syntax earlier, which is a good sign. Two people suggesting the same thing is promising.
Jan also suggested that this syntax allows binding the exception:
things[i] (except IndexError as exc: exc.args)
and multiple except terms:
things[i] (except IndexError as exc: exc.args, except NameError: "missing", except KeyError: None, )
One might also catch multiple exceptions in a single term:
things[i] (except IndexError,KeyError as exc: exc.args)
It's a tiny bit unusual to have a colon that doesn't introduce a block, but only a tiny bit. There is precedent:
lambda x, y: (x+y)/(x*y)
This suggests that perhaps the parentheses aren't needed unless you have multiple except parts:
things[i] (except IndexError, KeyError as exc: exc.args) things[i] except IndexError, KeyError as exc: exc.args
but I think they ought to be compulsory if you use multiple excepts. And of course they are useful for continuing over multiple lines.
I think this syntax looks good when used in compound expressions:
mylist = [23, lambda x: x+1, things[i] except IndexError: 42, ""] result = function(a, b except NameError: "undefined", c) result = function(a, b, c) except ValueError: float('nan') if something(x) except TypeError: None: block
Dicts are problematic. Should the dict colon bind more or less strongly than the except colon? I suggest we follow the same rule as for lambda:
adict = {lambda x: x+1: "value", key+1 except TypeError: 23: "value", }
Here is a torture-test for the syntax: can we combine it with an if-expression? I think we can, although you may need parentheses to disambiguate the expression:
something(x) (except TypeError: a if condition else b) ((something(x) if x else other(x)) (except ValueError: -1) something(x) if x else (other(x) except ValueError: -1)
Trying to do too much in a single expression is never exactly *beautiful*, but it can be read.
This does look a tiny bit like a function call, especially if you delete the space between the leading expression and the opening bracket:
# ugly, don't do this things[i](except IndexError: 42)
but that's not actually ambiguous, since the keyword except cannot be an argument to a function.
I like this. I think this is the first non-sucky syntax I've seen (sorry to everyone who proposed sucky syntax *wink*).
This has the advantage of putting the colon inside parens, where it's less likely to get confused with other uses of colons in the same line (by humans, if not by the computer).
Also it might be useful to be able to say
things.remove(i) (except ValueError: pass)
which would be equivalent to
things.remove(i) (except ValueError: None)
but would read more smoothly in cases where you're not interested in the value of the expression.
Certainly not! pass implies that *no return result is generated at all*, which is not possible in Python. Returning None is the right thing to do.
-- Steven _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Sat, Feb 15, 2014 at 08:24:09PM +0100, Philipp A. wrote:
hmm i still don’t like the colon. everywhere else in python, it means “new block follows”
That is incorrect. Did you read my full post? I specifically mentioned both dicts and lambda, both of which use colons in expressions, and don't start a new block.
and i’m sure nobody here wants to allow the following?
value = hovercraft() except EelsException: remove_eels() no_eels #this is assigned to “value” if we have too many eels
That's not the suggested syntax.
To start with, if you want to spread the expression over multiple lines, you need either line continuation backslashes, or round brackets:
value = hovercraft() (except EelsException: remove_eels() )
Secondly, you then place an unexpected "no_eels" statement after the expression. That will be a syntax error.
i like it in coffeescript and scala, but it’s not used in python, so we shouldn’t introduce it.
What is used in coffeescript and scala?
neither should we introduce a colon that *can’t* be followed by a block.
Dicts, lambda.
if we wanted the colon in any case, we’d need to do:
winner = germans_win() except EurekaException: return greeks
Certainly not. Do we write this?
mydict = {1: return 'a', 2: return 'b'}
value = hovercraft() except EelsException: remove_eels() return no_eels
This is not the suggested syntax. I suggest you read my post again, and notice that this is an *expression*. That is the whole point of the thread! If you want a block made up of multiple statements, use a try...except statement.
but i’m still partial to stomach_content = eat() except ThinMintError pass explode()
"pass" is a placeholder null statement, it has no relevance to try...except. You might just as well sensibly say
stomach_content = eat() except ThinMintError import explode() stomach_content = eat() except ThinMintError del explode() stomach_content = eat() except ThinMintError class explode() stomach_content = eat() except ThinMintError def explode() stomach_content = eat() except ThinMintError pass explode()
In all of these cases, I have picked a random keyword and just tossed it into the expression. None of them make any sense.
On 2014-02-15 19:57, Steven D'Aprano wrote:
On Sat, Feb 15, 2014 at 08:24:09PM +0100, Philipp A. wrote:
hmm i still don’t like the colon. everywhere else in python, it means “new block follows”
That is incorrect. Did you read my full post? I specifically mentioned both dicts and lambda, both of which use colons in expressions, and don't start a new block.
It's also used in slices.
and i’m sure nobody here wants to allow the following?
value = hovercraft() except EelsException: remove_eels() no_eels #this is assigned to “value” if we have too many eels
That's not the suggested syntax.
To start with, if you want to spread the expression over multiple lines, you need either line continuation backslashes, or round brackets:
value = hovercraft() (except EelsException: remove_eels() )
Secondly, you then place an unexpected "no_eels" statement after the expression. That will be a syntax error.
i like it in coffeescript and scala, but it’s not used in python, so we shouldn’t introduce it.
What is used in coffeescript and scala?
neither should we introduce a colon that *can’t* be followed by a block.
Dicts, lambda.
if we wanted the colon in any case, we’d need to do:
winner = germans_win() except EurekaException: return greeks
Certainly not. Do we write this?
mydict = {1: return 'a', 2: return 'b'}
value = hovercraft() except EelsException: remove_eels() return no_eels
This is not the suggested syntax. I suggest you read my post again, and notice that this is an *expression*. That is the whole point of the thread! If you want a block made up of multiple statements, use a try...except statement.
but i’m still partial to stomach_content = eat() except ThinMintError pass explode()
"pass" is a placeholder null statement, it has no relevance to try...except. You might just as well sensibly say
stomach_content = eat() except ThinMintError import explode() stomach_content = eat() except ThinMintError del explode() stomach_content = eat() except ThinMintError class explode() stomach_content = eat() except ThinMintError def explode() stomach_content = eat() except ThinMintError pass explode()
In all of these cases, I have picked a random keyword and just tossed it into the expression. None of them make any sense.
On 2014-02-15 21:08, MRAB wrote:
On 2014-02-15 19:57, Steven D'Aprano wrote:
On Sat, Feb 15, 2014 at 08:24:09PM +0100, Philipp A. wrote:
hmm i still don’t like the colon. everywhere else in python, it means “new block follows”
That is incorrect. Did you read my full post? I specifically mentioned both dicts and lambda, both of which use colons in expressions, and don't start a new block.
It's also used in slices.
[snip] I wonder if the reply is going to be: "Well, _apart_ from dicts, lambdas, and slices, everywhere else in Python...". :-)
2014-02-15 20:57 GMT+01:00 Steven D’Aprano steve@pearwood.info:
That is incorrect. Did you read my full post? I specifically mentioned both dicts and lambda, both of which use colons in expressions, and don’t start a new block.
oh, sorry, totally overlooked that.
i wouldn’t count dicts (and slices), as they are literals of sorts, but lambdas are exactly what we’re sidcussing about: a expression, instisting of sub-expression, one of which is after a colon.
forget my post. i now like the colon variant, as well as the “pass” and “try” variants, with no clear preference.
“pass” is a placeholder null statement, it has no relevance to try…except. You might just as well sensibly say
stomach_content = eat() except ThinMintError import explode() stomach_content = eat() except ThinMintError del explode() stomach_content = eat() except ThinMintError class explode() stomach_content = eat() except ThinMintError def explode() stomach_content = eat() except ThinMintError pass explode()
In all of these cases, I have picked a random keyword and just tossed it into the expression. None of them make any sense.
hah! now it’s my turn to smugly say “did you even read my post?!” ;)
2014-02-13 14:19 GMT+01:00 Philipp A. flying-sheep@web.de:
pass is only used to mean “do nothing” right now, and indeed that’s fitting: it’s just a border between EXC_TYPE [as EXC_NAME] and ALT_EXPR, *and its other meaning makes sense in english grammar*. Maybe even try? Like “use this, except if that doesn’t work, then try using the other thing”.
On Sun, Feb 16, 2014 at 11:16 PM, Philipp A. flying-sheep@web.de wrote:
i wouldn’t count dicts (and slices), as they are literals of sorts, but lambdas are exactly what we’re sidcussing about: a expression, instisting of sub-expression, one of which is after a colon.
Braces syntax for dicts is somewhere between an expression and a literal. Syntactically it's not a literal, as can be shown with dis:
def f():
return {1:2, 3:4}
dis.dis(f)
2 0 BUILD_MAP 2 3 LOAD_CONST 1 (2) 6 LOAD_CONST 2 (1) 9 STORE_MAP 10 LOAD_CONST 3 (4) 13 LOAD_CONST 4 (3) 16 STORE_MAP 17 RETURN_VALUE
But to the human, it's convenient to think of it as a "dict literal" in the same way that you can have a "string literal" or "float literal". You can compare something against a "literal", for instance:
f() == {3:4, 1:2}
True
However, it's not a literal, as it can have arbitrary expressions for both keys and values:
{int("123"):float("234")}
{123: 234.0}
[ Steven said: ] “pass” is a placeholder null statement, it has no relevance to try…except. You might just as well sensibly say
stomach_content = eat() except ThinMintError import explode() stomach_content = eat() except ThinMintError del explode() stomach_content = eat() except ThinMintError class explode() stomach_content = eat() except ThinMintError def explode() stomach_content = eat() except ThinMintError pass explode()
In all of these cases, I have picked a random keyword and just tossed it into the expression. None of them make any sense. [ Philipp responded: ] hah! now it’s my turn to smugly say “did you even read my post?!” ;)
It's worth noting that the person who originally suggested "pass" here was me, and I came up with it by the exact method Steven described: pulled up a list of keywords and picked one that seemed plausible enough to post... *as a joke*. It wasn't till it was picked up on as a plausible suggestion that I considered it at all seriously. It was my preferred form in the first draft; now, my preferred form is with the colon. The two-keyword notation lent itself well to parallels with if/else, but since chaining needs to be special anyway, this isn't going to be a simple operator.
(By the way, Steven, what on earth is a Thin Mint that could cause your eating to be so exceptional that your stomach explodes? The mind boggles...)
ChrisA
2014-02-16 14:12 GMT+01:00 Chris Angelico rosuav@gmail.com:
(By the way, Steven, what on earth is a Thin Mint that could cause your eating to be so exceptional that your stomach explodes? The mind boggles...)
Did you ever wonder why the language is called “Python” and why we use “spam and eggs” instead of “foo and bar”?
Let’s just say that if you eat as much as Mr Creosote, even a wafer thin mint might be the straw that breaks the camel’s back…
On Mon, Feb 17, 2014 at 12:12:30AM +1100, Chris Angelico wrote:
It's worth noting that the person who originally suggested "pass" here was me, and I came up with it by the exact method Steven described: pulled up a list of keywords and picked one that seemed plausible enough to post... *as a joke*.
You should say so in the PEP :-)
(By the way, Steven, what on earth is a Thin Mint that could cause your eating to be so exceptional that your stomach explodes? The mind boggles...)
It's a Monty Python reference. It's actually a wafer-thin afterdinner mint.
On Mon, Feb 17, 2014 at 4:37 AM, Steven D'Aprano steve@pearwood.info wrote:
(By the way, Steven, what on earth is a Thin Mint that could cause your eating to be so exceptional that your stomach explodes? The mind boggles...)
It's a Monty Python reference. It's actually a wafer-thin afterdinner mint.
Huh. I thought I was at least broadly familiar with MP, but that one's slipped me. Time to dig around on Youtube, I think!
In some contexts, the unfamiliar will send people to the dictionary [1]. Around the Python lists, the unfamiliar can send people to Youtube. I think that's doing fairly well. :)
ChrisA
[1] See eg the Dec 18th comment here: https://www.kickstarter.com/projects/lukewilson/500-old-christian-books-repu...
Philipp A. wrote:
hmm i still don’t like the colon. everywhere else in python, it means “new block follows”
No, it doesn't. There are at least two existing exceptions, lambdas and slice expressions.
and i’m sure nobody here wants to allow the following?
|value = hovercraft() except EelsException: remove_eels() no_eels #this is assigned to “value” if we have too many eels
neither should we introduce a colon that /can’t/ be followed by a block.
I don't think anyone is suggesting that. The colons in lambdas and slice expressions can't be followed by blocks either, and nobody seems to have difficulty with that.
Steven D'Aprano wrote:
This suggests that perhaps the parentheses aren't needed unless you have multiple except parts:
things[i] (except IndexError, KeyError as exc: exc.args) things[i] except IndexError, KeyError as exc: exc.args
Possibly not even necessary when there are multiple except clauses, if you don't require a comma between them:
things[i] except IndexError: 42 except KeyError: 17
Parsing may be easier without the parens as well, since otherwise it looks like a function call when you hit the '(' until you see the 'except' after it. (I *think* that Python's parser could be made to handle that, but I'd have to experiment to find out for sure.)
Here is a torture-test for the syntax: can we combine it with an if-expression? I think we can, although you may need parentheses to disambiguate the expression:
something(x) (except TypeError: a if condition else b)
The paren-less version of this would be
something(x) except TypeError: a if condition else b
the interpretation of which would depend on what relative precedence we decide on between 'except' and 'if'.
Parens can still be used to clarify, though:
(something(x) except TypeError: a) if condition else b
something(x) except TypeError: (a if condition else b)
This does look a tiny bit like a function call, especially if you delete the space between the leading expression and the opening bracket:
# ugly, don't do this things[i](except IndexError: 42)
Yes, that's why I'm leaning towards the paren-less version.
On Sun, Feb 16, 2014 at 10:17 AM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
This does look a tiny bit like a function call, especially if you delete the space between the leading expression and the opening bracket:
# ugly, don't do this things[i](except IndexError: 42)
Yes, that's why I'm leaning towards the paren-less version.
I'm not liking the parenthesized version here, because the 'except' expression has to know how much of the preceding expression to modify. With a function call:
expression(args)
the expression is fully evaluated first, and then whatever it returns gets called; with the except expression, a try block has to be set up somewhere. This is more similar to if-else than to a function call, in that the expression before the 'if' isn't evaluated until the condition has been tested.
Parens could go around the whole thing:
(thing[i] except IndexError: 42) (1/x if x else "Div by 0")
but not around the except clause:
thing[i] (except IndexError: 42) # Nope 1/x (if x else "Div by 0") # Nope
Incidentally, I'm looking at this being able to chain quite nicely:
((expr except Exception1: default1) except Exception2: default2)
Ideally the peephole optimizer could set up a single try/except structure for both, but syntactically, I'm seeing this as the way the operator associates.
I've mailed the first-draft PEP to peps@; is it appropriate to post it here as well, or should I wait to hear back from peps@?
ChrisA
On 2014-02-15 23:46, Chris Angelico wrote:
On Sun, Feb 16, 2014 at 10:17 AM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
This does look a tiny bit like a function call, especially if you delete the space between the leading expression and the opening bracket:
# ugly, don't do this things[i](except IndexError: 42)
Yes, that's why I'm leaning towards the paren-less version.
I'm not liking the parenthesized version here, because the 'except' expression has to know how much of the preceding expression to modify. With a function call:
expression(args)
the expression is fully evaluated first, and then whatever it returns gets called; with the except expression, a try block has to be set up somewhere. This is more similar to if-else than to a function call, in that the expression before the 'if' isn't evaluated until the condition has been tested.
Parens could go around the whole thing:
(thing[i] except IndexError: 42) (1/x if x else "Div by 0")
but not around the except clause:
thing[i] (except IndexError: 42) # Nope 1/x (if x else "Div by 0") # Nope
Incidentally, I'm looking at this being able to chain quite nicely:
((expr except Exception1: default1) except Exception2: default2)
You'll also need to note that:
((expr except Exception1: default1) except Exception2: default2)
is not the same as:
(expr except Exception1: default1 except Exception2: default2)
The first will also catch Exception2 if default1 raises it, whereas the second will catch Exception2 only if expr raises it and it hasn't been caught by the preceding 'except'.
Ideally the peephole optimizer could set up a single try/except structure for both, but syntactically, I'm seeing this as the way the operator associates.
I've mailed the first-draft PEP to peps@; is it appropriate to post it here as well, or should I wait to hear back from peps@?
On Sun, Feb 16, 2014 at 11:35 AM, MRAB python@mrabarnett.plus.com wrote:
You'll also need to note that:
((expr except Exception1: default1) except Exception2: default2)
is not the same as:
(expr except Exception1: default1 except Exception2: default2)
The first will also catch Exception2 if default1 raises it, whereas the second will catch Exception2 only if expr raises it and it hasn't been caught by the preceding 'except'.
Ooh. Good catch. This is not just an optimization, it's a specific piece of syntax: chaining 'except' blocks MUST catch only from the original.
I'm also switching around my Proposal and Alternative Proposals a bit, as I'm currently leaning towards the colon rather than a keyword.
Lots of edits to the PEP draft, but so far just kept local to my own computer. At what point (and in what way) should I start sharing those edits?
ChrisA
On Sun, Feb 16, 2014 at 12:35:59AM +0000, MRAB wrote:
On 2014-02-15 23:46, Chris Angelico wrote:
On Sun, Feb 16, 2014 at 10:17 AM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
This does look a tiny bit like a function call, especially if you delete the space between the leading expression and the opening bracket:
# ugly, don't do this things[i](except IndexError: 42)
Yes, that's why I'm leaning towards the paren-less version.
I'm not liking the parenthesized version here, because the 'except' expression has to know how much of the preceding expression to modify.
I don't think this is terribly different from the if-expression. The parser sees an expression:
1/x ...
and doesn't know it is part of an if-expression until it has read on and seen the "if":
1/x if x != 0 ...
so I don't expect this to be a problem.
[...]
Parens could go around the whole thing:
Of course they can, that's just normal parentheses-as-grouping :-)
(thing[i] except IndexError: 42) (1/x if x else "Div by 0")
but not around the except clause:
thing[i] (except IndexError: 42) # Nope 1/x (if x else "Div by 0") # Nope
I disagree about prohibiting this. I think it actually makes it easier to read in the case of multiple except terms. Since they ought to be separated by commas, and stylistically they ought to be split one-per-line, I prefer the bracket-less version when there is only a single except:
thing(a, b) except ThisError, ThatError: default
and brackets when there are more than one:
thing(a, b) (except ThisError, ThatError: default, except SpamError, EggsError: something, except OutOfCheeseError: different_thing, )
although I'm happy for the parens to be optional in the second case if the parser can manage it, assuming that the parser can disambigulate all the various uses of commas.
I can't think of any places where parens are explicitly prohibited. They used to be prohibited in imports, but that's no longer the case. One should always be allowed to use parens for grouping and implicit line continuations, nearly everywhere.
How does this suggested syntax look with Raymond's recent example? Raymond regretted that max and min now take a "default" argument, as of Python 3.4. Let's compare:
result = max(some_numbers, key=foo, default=float('nan'))
result = max(some_numbers, key=foo) except ValueError: float('nan')
Looks damn good to me!
Incidentally, I'm looking at this being able to chain quite nicely:
((expr except Exception1: default1) except Exception2: default2)
This is equivalent to:
try: try: result = expr except Exception1: result = default1 except Exception2: result = default2
You'll also need to note that:
((expr except Exception1: default1) except Exception2: default2)
is not the same as:
(expr except Exception1: default1 except Exception2: default2)
I disklike the lack of comma between except clauses. I think that ought to be written as:
(expr except Exception1: default1, except Exception2: default2)
modulo the above argument about parentheses.
The first will also catch Exception2 if default1 raises it, whereas the second will catch Exception2 only if expr raises it and it hasn't been caught by the preceding 'except'.
Correct. The two are quite different.
Ideally the peephole optimizer could set up a single try/except structure for both, but syntactically, I'm seeing this as the way the operator associates.
I've mailed the first-draft PEP to peps@; is it appropriate to post it here as well, or should I wait to hear back from peps@?
I think it is perfectly appropriate to post a draft PEP here.
On Sun, Feb 16, 2014 at 2:35 PM, Steven D'Aprano steve@pearwood.info wrote:
On Sun, Feb 16, 2014 at 12:35:59AM +0000, MRAB wrote:
On 2014-02-15 23:46, Chris Angelico wrote:
things[i](except IndexError: 42)
I'm not liking the parenthesized version here, because the 'except' expression has to know how much of the preceding expression to modify.
I don't think this is terribly different from the if-expression. The parser sees an expression:
1/x ...
and doesn't know it is part of an if-expression until it has read on and seen the "if":
1/x if x != 0 ...
so I don't expect this to be a problem.
but not around the except clause:
thing[i] (except IndexError: 42) # Nope 1/x (if x else "Div by 0") # Nope
I disagree about prohibiting this. I think it actually makes it easier to read in the case of multiple except terms.
The if/else version is currently an error, because the parens are breaking the sequence up:
1/x (if x else "Div by 0")
SyntaxError: invalid syntax
(highlighting the "if" as the invalid part)
The way I understand it, the entire expression from 1/x to "Div by 0" has to be parsed as a unit. Putting parens around part of it breaks that. The same goes for the exception-catching version, unless the parens are specifically a part of the syntax. Since you can just put parens around the whole expression (to help with wrapping etc), I don't see a compelling reason to put them around just the except bit.
Since they ought to be separated by commas, and stylistically they ought to be split one-per-line, I prefer the bracket-less version when there is only a single except:
thing(a, b) except ThisError, ThatError: default
and brackets when there are more than one:
thing(a, b) (except ThisError, ThatError: default, except SpamError, EggsError: something, except OutOfCheeseError: different_thing, )
Move the open parenthesis to before thing(a, b) and you have what I would be recommending.
I'm +0 on the explicit comma separation between except clauses. Will add that to the proposal, complete with a caution about it being a potential bug magnet.
I can't think of any places where parens are explicitly prohibited. They used to be prohibited in imports, but that's no longer the case. One should always be allowed to use parens for grouping and implicit line continuations, nearly everywhere.
They're prohibited in the middles of things. You can't, for instance, put parens around a few of a function's arguments (if they're pure positional then it's legal but has other meaning, and other notations are syntax errors). The inside of ( ) generally has to be a valid, parseable expression.
How does this suggested syntax look with Raymond's recent example? Raymond regretted that max and min now take a "default" argument, as of Python 3.4. Let's compare:
result = max(some_numbers, key=foo, default=float('nan')) result = max(some_numbers, key=foo) except ValueError: float('nan')
Looks damn good to me!
Yeah. I especially like how this means the author of max() doesn't need to think about this possibility. (I'm still trying to come up with, in my head, an external means of chaining method calls - one that makes sense and looks clean. Same reason; the method author shouldn't have to think in terms of chaining.)
Incidentally, I'm looking at this being able to chain quite nicely:
((expr except Exception1: default1) except Exception2: default2)
This is equivalent to:
try: try: result = expr except Exception1: result = default1 except Exception2: result = default2
Yes, as MRAB pointed out. I've updated the PEP to note this. The syntactic form has to itself understand chaining.
I've mailed the first-draft PEP to peps@; is it appropriate to post it here as well, or should I wait to hear back from peps@?
I think it is perfectly appropriate to post a draft PEP here.
Okay. Will post that separately to this.
ChrisA
PEP: XXX Title: Exception-catching expressions Version: $Revision$ Last-Modified: $Date$ Author: Chris Angelico rosuav@gmail.com Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 15-Feb-2014 Python-Version: 3.5 Post-History: 16-Feb-2014
Abstract ========
Just as PEP 308 introduced a means of value-based conditions in an expression, this system allows exception-based conditions to be used as part of an expression.
Motivation ==========
A number of functions and methods have parameters which will cause them to return a specified value instead of raising an exception. The current system is ad-hoc and inconsistent, and requires that each function be individually written to have this functionality; not all support this.
* dict.get(key, default) - second positional argument in place of KeyError
* next(iter, default) - second positional argument in place of StopIteration
* list.pop() - no way to return a default
(TODO: Get more examples. I know there are some but I can't think of any now.)
Rationale =========
The current system requires that a function author predict the need for a default, and implement support for it. If this is not done, a full try/except block is needed.
Note that the specific syntax is open to three metric tons of bike-shedding.
The proposal may well be rejected, but even then is not useless; it can be maintained as a collection of failed syntaxes for expression exceptions.
Proposal ========
Just as the 'or' operator and the three part 'if-else' expression give short circuiting methods of catching a falsy value and replacing it, this syntax gives a short-circuiting method of catching an exception and replacing it.
This currently works:: lst = [1, 2, None, 3] value = lst[2] or "No value"
The proposal adds this:: lst = [1, 2] value = lst[2] except IndexError: "No value"
The exception object can be captured just as in a normal try/except block:: # Return the next yielded or returned value from a generator value = next(it) except StopIteration as e: e.args[0]
This is effectively equivalent to:: try: _ = next(it) except StopIteration as e: _ = e.args[0] value = _
This ternary operator would be between lambda and if/else in precedence.
Chaining --------
Multiple 'except' keywords can be used, and they will all catch exceptions raised in the original expression (only):: value = (expr except Exception1 [as e]: default1 except Exception2 [as e]: default2 # ... except ExceptionN [as e]: defaultN )
This is not the same as either parenthesized form:: value = (("Yield: "+next(it) except StopIteration as e: "End: "+e.args[0]) except TypeError: "Error: Non-string returned or raised") value = (next(it) except StopIteration as e: (e.args[0] except IndexError: None))
The first form will catch an exception raised in either the original expression or in the default expression; the second form will catch ONLY one raised by the default expression. All three effects have their uses.
Alternative Proposals =====================
Discussion on python-ideas brought up the following syntax suggestions:: value = expr except default if Exception [as e] value = expr except default for Exception [as e] value = expr except default from Exception [as e] value = expr except Exception [as e] return default value = expr except (Exception [as e]: default) value = expr except Exception [as e] try default value = expr except Exception [as e] continue with default value = default except Exception [as e] else expr value = try expr except Exception [as e]: default value = expr except Exception [as e] pass default
In all cases, default is an expression which will not be evaluated unless an exception is raised; if 'as' is used, this expression may refer to the exception object.
It has also been suggested that a new keyword be created, rather than reusing an existing one. Such proposals fall into the same structure as the last form, but with a different keyword in place of 'pass'. Suggestions include 'then', 'when', and 'use'.
Open Issues ===========
finally clause --------------
Should the except expression be able to have a finally clause? No form of the proposal so far has included finally.
Commas between multiple except clauses -------------------------------------- Where there are multiple except clauses, should they be separated by commas? It may be easier for the parser, that way:: value = (expr except Exception1 [as e]: default1, except Exception2 [as e]: default2, # ... except ExceptionN [as e]: defaultN, ) with an optional comma after the last, as per tuple rules. Downside: Omitting the comma would be syntactically valid, and would have almost identical semantics, but would nest the entire preceding expression in its exception catching rig - a matching exception raised in the default clause would be caught by the subsequent except clause. As this difference is so subtle, it runs the risk of being a major bug magnet.
Copyright =========
This document has been placed in the public domain.
.. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 coding: utf-8
On 16 February 2014 14:04, Chris Angelico rosuav@gmail.com wrote:
PEP: XXX Title: Exception-catching expressions Version: $Revision$ Last-Modified: $Date$ Author: Chris Angelico rosuav@gmail.com Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 15-Feb-2014 Python-Version: 3.5 Post-History: 16-Feb-2014
Abstract
Just as PEP 308 introduced a means of value-based conditions in an expression, this system allows exception-based conditions to be used as part of an expression.
Thanks for putting this together, Chris! :)
Motivation
A number of functions and methods have parameters which will cause them to return a specified value instead of raising an exception. The current system is ad-hoc and inconsistent, and requires that each function be individually written to have this functionality; not all support this.
dict.get(key, default) - second positional argument in place of KeyError
next(iter, default) - second positional argument in place of StopIteration
list.pop() - no way to return a default
(TODO: Get more examples. I know there are some but I can't think of any now.)
* seq[index] - no way to return a default * str.find vs str.index (hardcoded -1 result vs exception) * min, max - keyword-only default in place of ValueError for empty iterable
There's also a use case at the interactive prompt:
>>> e = 1/0 except ZeroDivisionError as e: e
(Currently, capturing an exception at the interactive prompt to poke around at the details is annoyingly painful, because "_" doesn't capture exceptions by detauls)
Proposal
Just as the 'or' operator and the three part 'if-else' expression give short circuiting methods of catching a falsy value and replacing it, this syntax gives a short-circuiting method of catching an exception and replacing it.
This section (or a separate "Detailed Semantics" one) is missing a detailed specification of the semantics, in particular:
- what scope are subexpressions evaluated in? - if the "as" clause is permitted, what is the scope of that name binding? - if the current scope, then how does that square with the changes to list comprehensions in 3.0 to always use a new scope for the iteration variables (the same as generator expressions). Keep in mind that the except expression will need to preserve the Python 3 except clause behaviour of unbinding the exception name after evaluating the subexpression - if a new scope, what impact does that have on what subexpressions are valid, especially at class scope? - what happens if an except expression is used as part of the except clause in an ordinary try statement, or another except expression?
(I suspect defining these except expressions as creating a new transient scope, akin to comprehensions and generator expressions, will actually avoid a variety of issues, in particular, preventing leaking the name of the bound exception into the containing scope. I also suspect a transient scope would make the PEP's currently preferred colon based notation more palatable to some readers)
Cheers, Nick.
On Mon, Feb 17, 2014 at 1:37 AM, Nick Coghlan ncoghlan@gmail.com wrote:
Thanks for putting this together, Chris! :)
Thanks for the edit advice!
This section (or a separate "Detailed Semantics" one) is missing a detailed specification of the semantics, in particular:
- what happens if an except expression is used as part of the except
clause in an ordinary try statement, or another except expression?
That should be fairly obvious - it's like nesting try/except. Does it need to be said?
- what scope are subexpressions evaluated in?
- if the "as" clause is permitted, what is the scope of that name binding?
- if the current scope, then how does that square with the changes to
list comprehensions in 3.0 to always use a new scope for the iteration variables (the same as generator expressions). Keep in mind that the except expression will need to preserve the Python 3 except clause behaviour of unbinding the exception name after evaluating the subexpression
- if a new scope, what impact does that have on what subexpressions
are valid, especially at class scope?
(I suspect defining these except expressions as creating a new transient scope, akin to comprehensions and generator expressions, will actually avoid a variety of issues, in particular, preventing leaking the name of the bound exception into the containing scope. I also suspect a transient scope would make the PEP's currently preferred colon based notation more palatable to some readers)
Yeah, now, this part is a problem. Subsequently to the version posted, I've added an Open Issues subsection regarding scope.
""" Scope of default expressions and 'as' -------------------------------------
In a try/except block, the use of 'as' to capture the exception object creates a local name binding, and implicitly deletes that binding in a finally clause. As 'finally' is not a part of this proposal (see below), this makes it tricky to describe; also, this use of 'as' gives a way to create a name binding in an expression context. Should the default clause have an inner scope in which the name exists, shadowing anything of the same name elsewhere? Should it behave the same way the statement try/except does, and unbind the name? Should it bind the name and leave it bound? (Almost certainly not; this behaviour was changed in Python 3 for good reason.) """
It's a knotty problem. I'm currently inclined to the inner scope idea (like a comprehension), but that's inconsistent with the statement form of try/except. Is that difference going to confuse people?
e = 1234 [e for e in (1,)]
[1]
e
1234
try:
1/0 except ZeroDivisionError as e: pass
e
Traceback (most recent call last): File "<pyshell#147>", line 1, in <module> e NameError: name 'e' is not defined
Also, what are the implications on the inside? Disassembling a function with a list comp shows that it calls an inner function, which I presume is the only way CPython can create an inner scope. Does the default-expression then have to be a separate function, and if so, what does that mean?
Alternatively, what would it be like if an expression could do a name binding and unbinding (consistently with the statement form)? Would that confuse people no end?
ChrisA
On 02/16/2014 01:52 PM, Chris Angelico wrote:
On Mon, Feb 17, 2014 at 1:37 AM, Nick Coghlan ncoghlan@gmail.com wrote:
- what happens if an except expression is used as part of the except
clause in an ordinary try statement, or another except expression?
That should be fairly obvious - it's like nesting try/except. Does it need to be said?
When writing a PEP about something new, *nothing* is obvious.
-- ~Ethan~
On Mon, Feb 17, 2014 at 08:52:41AM +1100, Chris Angelico wrote:
Scope of default expressions and 'as'
In a try/except block, the use of 'as' to capture the exception object creates a local name binding, and implicitly deletes that binding in a finally clause. As 'finally' is not a part of this proposal (see below), this makes it tricky to describe; also, this use of 'as' gives a way to create a name binding in an expression context. Should the default clause have an inner scope in which the name exists, shadowing anything of the same name elsewhere? Should it behave the same way the statement try/except does, and unbind the name?
I consider that a wart, and would not like to repeat that unless absolutely necessary.
Should it bind the name and leave it bound? (Almost certainly not; this behaviour was changed in Python 3 for good reason.)
I think the first option is best: avoid letting the "as" name leak out into the local scope, just like gen expressions and list comps:
py> i = "spam" py> x = [i+1 for i in range(10)] py> i 'spam'
It's a knotty problem. I'm currently inclined to the inner scope idea (like a comprehension), but that's inconsistent with the statement form of try/except. Is that difference going to confuse people?
I didn't think so. I didn't even realise that exceptions unbind the "as" name, instead of using a separate scope. I don't expect anyone is *relying* on that unbind-the-name behaviour, but even if they are, they can just keep using the try...except statement form.
Alternatively, what would it be like if an expression could do a name binding and unbinding (consistently with the statement form)? Would that confuse people no end?
It would confuse, surprise and annoy me. I presume there are good reasons why the try...except statement does an unbind instead of using a new scope, but I really don't want to repeat that behaviour.
On Mon, Feb 17, 2014 at 4:29 PM, Steven D'Aprano steve@pearwood.info wrote:
On Mon, Feb 17, 2014 at 08:52:41AM +1100, Chris Angelico wrote:
Scope of default expressions and 'as'
In a try/except block, the use of 'as' to capture the exception object creates a local name binding, and implicitly deletes that binding in a finally clause. As 'finally' is not a part of this proposal (see below), this makes it tricky to describe; also, this use of 'as' gives a way to create a name binding in an expression context. Should the default clause have an inner scope in which the name exists, shadowing anything of the same name elsewhere? Should it behave the same way the statement try/except does, and unbind the name?
I consider that a wart, and would not like to repeat that unless absolutely necessary.
Yeah, the unbinding is really weird. I didn't know about it until I started experimenting, too... and then went digging in the docs and found out that it implicitly creates a 'finally' that dels the name.
It's a knotty problem. I'm currently inclined to the inner scope idea (like a comprehension), but that's inconsistent with the statement form of try/except. Is that difference going to confuse people?
I didn't think so. I didn't even realise that exceptions unbind the "as" name, instead of using a separate scope. I don't expect anyone is *relying* on that unbind-the-name behaviour, but even if they are, they can just keep using the try...except statement form.
The inner scope does seem to demand a function call, though.
Maybe this is a good opportunity to introduce the concept of "sub-scopes" for specific constructs. Comprehensions and 'as' clauses (I doubt anyone will object to the statement try/except being brought in line with this) could then use a sub-scope inside the existing function; it'd affect only the LOAD_FAST opcode, I think - assignment could delete it from the sub-scope and put it into the main scope. This would mean that:
try: f() except Exception as e: e = e print(e)
would print out the exception. But that would be a completely separate PEP. I'd like it to happen, but it's not part of exception expressions.
ChrisA
Chris Angelico wrote:
The inner scope does seem to demand a function call, though.
A function call isn't *required* to achieve the effect of an inner scope, as far as I know. It's just that implementing an inner scope for list comprehensions without it would have required extensive changes to the way the bytecode compiler currently works, and using a function was easier.
Chris Angelico wrote:
Maybe this is a good opportunity to introduce the concept of "sub-scopes" for specific constructs. Comprehensions and 'as' clauses (I doubt anyone will object to the statement try/except being brought in line with this) could then use a sub-scope inside the existing function; it'd affect only the LOAD_FAST opcode, I think - assignment could delete it from the sub-scope and put it into the main scope.
It *should* be possible to handle this entirely at compile time. Conceptually, all you need to do is rename the names in the subscope so that they don't clash with anything in the outer scope, and then generate bytecode as usual.
I haven't studied the compiler closely enough to tell how difficult this would be to achieve, though. Whoever implemented inner scopes for list comprehensions reportedly found it harder than he liked at the time.
On 18 Feb 2014 08:57, "Greg Ewing" greg.ewing@canterbury.ac.nz wrote:
Chris Angelico wrote:
Maybe this is a good opportunity to introduce the concept of "sub-scopes" for specific constructs. Comprehensions and 'as' clauses (I doubt anyone will object to the statement try/except being brought in line with this) could then use a sub-scope inside the existing function; it'd affect only the LOAD_FAST opcode, I think - assignment could delete it from the sub-scope and put it into the main scope.
It *should* be possible to handle this entirely at compile time. Conceptually, all you need to do is rename the names in the subscope so that they don't clash with anything in the outer scope, and then generate bytecode as usual.
I haven't studied the compiler closely enough to tell how difficult this would be to achieve, though. Whoever implemented inner scopes for list comprehensions reportedly found it harder than he liked at the time.
That was me, and it's handling nested closures correctly that gets excruciatingly difficult. In theory, the compiler has enough info to figure out the details and generate appropriate inline code, but in practice, reusing the existing closure support made the change so much simpler to implement that was the option I eventually chose.
While that simplicity of implementation did come at the cost of making the behaviour at class scope a bit quirky, those quirks already existed for generator expressions, so they even had the virtue of consistency in their favour.
Cheers, Nick.
-- Greg
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Tue, Feb 18, 2014 at 10:54 AM, Nick Coghlan ncoghlan@gmail.com wrote:
I haven't studied the compiler closely enough to tell how difficult this would be to achieve, though. Whoever implemented inner scopes for list comprehensions reportedly found it harder than he liked at the time.
That was me, and it's handling nested closures correctly that gets excruciatingly difficult. In theory, the compiler has enough info to figure out the details and generate appropriate inline code, but in practice, reusing the existing closure support made the change so much simpler to implement that was the option I eventually chose.
Thanks Nick. Useful explanation, and now linked to in the PEP draft.
https://github.com/Rosuav/ExceptExpr/raw/master/pep.txt
ChrisA
On Tue, Feb 18, 2014 at 11:06 AM, Chris Angelico rosuav@gmail.com wrote:
https://github.com/Rosuav/ExceptExpr/raw/master/pep.txt
ChrisA
If a core committer who's active in this discussion has the time, could one please assign a number and commit this into the PEPs repo?
Thanks!
ChrisA
On Mon, Feb 17, 2014 at 08:52:41AM +1100, Chris Angelico wrote:
Should the default clause have an inner scope in which the name exists, shadowing anything of the same name elsewhere? Should it behave the same way the statement try/except does, and unbind the name?
We could sidestep the whole problem by not allowing an 'as' clause at all. I don't think it would be unreasonable to require the use of a try statement if you want to do anything that fancy.
On Tue, Feb 18, 2014 at 9:45 AM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
On Mon, Feb 17, 2014 at 08:52:41AM +1100, Chris Angelico wrote:
Should the default clause have an inner scope in which the name exists, shadowing anything of the same name elsewhere? Should it behave the same way the statement try/except does, and unbind the name?
We could sidestep the whole problem by not allowing an 'as' clause at all. I don't think it would be unreasonable to require the use of a try statement if you want to do anything that fancy.
I'd accept that if and only if it makes a huge difference to the implementability of the proposal. Conceptually, it's easy to say "inner scope". If it turns out that it's really REALLY hard to do that, then rejecting 'as' could be a fall-back. But I'd really rather keep it.
The inner scope does seem to demand a function call, though.
A function call isn't *required* to achieve the effect of an inner scope, as far as I know. It's just that implementing an inner scope for list comprehensions without it would have required extensive changes to the way the bytecode compiler currently works, and using a function was easier.
Interesting. I'll reword that section a bit, then. It may be that adding another use-case for inner scopes will make them more worth implementing, and then everything (comprehensions, except statements, and except expressions) can use them.
It might also be worth using an inner scope for a 'with' statement, too. Every block statement that uses 'as' could introduce a sub-scope. But that is *definitely* outside the, uh, scope of this PEP.
ChrisA
Chris Angelico wrote:
Conceptually, it's easy to say "inner scope". If it turns out that it's really REALLY hard to do that, then rejecting 'as' could be a fall-back. But I'd really rather keep it.
Do you have any non-contrived use cases in mind for 'as' in an except-expression? I'm having trouble thinking of any.
On Tue, Feb 18, 2014 at 5:23 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
Chris Angelico wrote:
Conceptually, it's easy to say "inner scope". If it turns out that it's really REALLY hard to do that, then rejecting 'as' could be a fall-back. But I'd really rather keep it.
Do you have any non-contrived use cases in mind for 'as' in an except-expression? I'm having trouble thinking of any.
There are a few in the current PEP draft now. Fetching info out of an exception isn't uncommon.
ChrisA
On Tue, Feb 18, 2014 at 5:27 PM, Chris Angelico rosuav@gmail.com wrote:
There are a few in the current PEP draft now. Fetching info out of an exception isn't uncommon.
Oops, meant to include the link.
https://raw2.github.com/Rosuav/ExceptExpr/master/pep.txt
ChrisA
Chris Angelico wrote:
On Tue, Feb 18, 2014 at 5:23 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
Do you have any non-contrived use cases in mind for 'as' in an except-expression? I'm having trouble thinking of any.
There are a few in the current PEP draft now. Fetching info out of an exception isn't uncommon.
Not in general, but in situations where you need the conciseness of an except-expression?
The examples in the PEP all seem to be variations on
value = next(it) except StopIteration as e: e.args[0]
which seems a bit contrived. It's hard to think of a situation where you'd want to treat the yielded values and the return value of a generator in the same way.
On Tue, Feb 18, 2014 at 6:12 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
Not in general, but in situations where you need the conciseness of an except-expression?
The examples in the PEP all seem to be variations on
value = next(it) except StopIteration as e: e.args[0]
which seems a bit contrived. It's hard to think of a situation where you'd want to treat the yielded values and the return value of a generator in the same way.
There's another one based on retrieving a document or its error text, and showing that to a human. Beyond that, I really need other people to make suggestions; there are those here who spend 99% of their time writing Python code and know all the ins and outs of the libraries, but I am not them.
ChrisA
On 18 February 2014 07:22, Chris Angelico rosuav@gmail.com wrote:
On Tue, Feb 18, 2014 at 6:12 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
Not in general, but in situations where you need the conciseness of an except-expression?
The examples in the PEP all seem to be variations on
value = next(it) except StopIteration as e: e.args[0]
which seems a bit contrived. It's hard to think of a situation where you'd want to treat the yielded values and the return value of a generator in the same way.
There's another one based on retrieving a document or its error text, and showing that to a human. Beyond that, I really need other people to make suggestions; there are those here who spend 99% of their time writing Python code and know all the ins and outs of the libraries, but I am not them.
That was about the only even vaguely compelling one to me, and even then I'm not convinced. Reproducing it here (btw, spacing out the proposed and current syntax would be a huge help):
Set a PyGTK label to a human-readable result from fetching a URL::
display.set_text( urllib.request.urlopen(url) except urllib.error.HTTPError as e: "Error %d: %s"%(x.getcode(), x.msg) except (ValueError, urllib.error.URLError) as e: "Invalid URL: "+str(e) )
try: display.set_text(urllib.request.urlopen(url)) except urllib.error.HTTPError as e: display.set_text("Error %d: %s"%(x.getcode(), x.msg)) except (ValueError, urllib.error.URLError) as e: display.set_text("Invalid URL: "+str(e))
However, your "current syntax" is not what I'd write at all - why not use a temporary variable, as in:
try: text = urllib.request.urlopen(url) except urllib.error.HTTPError as e: text = "Error %d: %s"%(x.getcode(), x.msg) except (ValueError, urllib.error.URLError) as e: text = "Invalid URL: "+str(e) display.set_text(text)
That seems much clearer and natural to me than either of your two examples. At this point the discussion starts to remind me of the "we need multiline lambda" discussions, which fall apart when trying to find compelling use cases where a simple def using a named function wouldn't be better.
I really think that this proposal needs to focus on the short, simple use cases and *not* worry about too much generality. For example:
sum(x[3] except 0 for x in list_of_tuples)
Note that when your controlled expression is sufficiently small (an index lookup here) you don't even need an exception name, because you'll never *get* anything except IndexError. And yes, I know this is naive and I know that function calls are the obvious extension and you probably need the exception type there, but my point is that if you focus on the simplest case only, and reject any extension of scope that isn't backed by a genuine real-world use case that someone can show you in actual code, you'll end up with a much tighter proposal.
I would actually be interested in being pointed at real-world code that wouldn't work fine with only a "catch everything" option. Note that the urllib.request example above doesn't bother me because (a) the multiline version with a named variable suits me fine, and (b) if I want something shorter all I need to do is use more generic error text: urllib.request.urlopen(url) except "Cannot fetch " + url.
Raymond Hettinger often evaluates proposals like this by going through the standard library looking for code that would be improved by converting to use the new syntax. I suspect that this would be a good exercise for this proposal (grepping for except in the stdlib should be easy enough to start with, and would give you a pretty long list of examples you can look at :-)
Paul
On 2014-02-18 08:00, Paul Moore wrote:
On 18 February 2014 07:22, Chris Angelico rosuav@gmail.com wrote:
On Tue, Feb 18, 2014 at 6:12 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
Not in general, but in situations where you need the conciseness of an except-expression?
The examples in the PEP all seem to be variations on
value = next(it) except StopIteration as e: e.args[0]
which seems a bit contrived. It's hard to think of a situation where you'd want to treat the yielded values and the return value of a generator in the same way.
There's another one based on retrieving a document or its error text, and showing that to a human. Beyond that, I really need other people to make suggestions; there are those here who spend 99% of their time writing Python code and know all the ins and outs of the libraries, but I am not them.
That was about the only even vaguely compelling one to me, and even then I'm not convinced. Reproducing it here (btw, spacing out the proposed and current syntax would be a huge help):
Set a PyGTK label to a human-readable result from fetching a URL::
display.set_text( urllib.request.urlopen(url) except urllib.error.HTTPError as e: "Error %d: %s"%(x.getcode(), x.msg) except (ValueError, urllib.error.URLError) as e: "Invalid URL: "+str(e) ) try: display.set_text(urllib.request.urlopen(url)) except urllib.error.HTTPError as e: display.set_text("Error %d: %s"%(x.getcode(), x.msg)) except (ValueError, urllib.error.URLError) as e: display.set_text("Invalid URL: "+str(e))
However, your "current syntax" is not what I'd write at all - why not use a temporary variable, as in:
try: text = urllib.request.urlopen(url) except urllib.error.HTTPError as e: text = "Error %d: %s"%(x.getcode(), x.msg) except (ValueError, urllib.error.URLError) as e: text = "Invalid URL: "+str(e) display.set_text(text)
That seems much clearer and natural to me than either of your two examples. At this point the discussion starts to remind me of the "we need multiline lambda" discussions, which fall apart when trying to find compelling use cases where a simple def using a named function wouldn't be better.
I really think that this proposal needs to focus on the short, simple use cases and *not* worry about too much generality. For example:
sum(x[3] except 0 for x in list_of_tuples)
Shouldn't that be:
sum(x[3] except: 0 for x in list_of_tuples)
(colon added)? [snip]
On 18 February 2014 12:49, MRAB python@mrabarnett.plus.com wrote:
I really think that this proposal needs to focus on the short, simple use cases and *not* worry about too much generality. For example:
sum(x[3] except 0 for x in list_of_tuples)
Shouldn't that be:
sum(x[3] except: 0 for x in list_of_tuples)
(colon added)?
Only if you feel that a colon is necessary for the syntax. I don't :-)
Paul.
On Wed, Feb 19, 2014 at 12:00 AM, Paul Moore p.f.moore@gmail.com wrote:
On 18 February 2014 12:49, MRAB python@mrabarnett.plus.com wrote:
I really think that this proposal needs to focus on the short, simple use cases and *not* worry about too much generality. For example:
sum(x[3] except 0 for x in list_of_tuples)
Shouldn't that be:
sum(x[3] except: 0 for x in list_of_tuples)
(colon added)?
Only if you feel that a colon is necessary for the syntax. I don't :-)
The colon is necessary if the new syntax should allow the specification of the exception. If it's always equivalent to a bare except that doesn't capture the exception object, then it'd be unnecessary.
Personally, I don't want to create a special syntax that does something that's strongly discouraged. I'm open to argument that the expression-except should accept just a single except clause; I'm open to argument that it shouldn't be able to capture (due to complications of creating sub-scopes), though that's more a nod to implementation/specification difficulty than any conceptual dislike of the syntax (it's clean, it's consistent, it's sometimes useful); but I don't want to narrow this down to always having to catch everything.
ChrisA
On 18 February 2014 13:37, Chris Angelico rosuav@gmail.com wrote:
On Wed, Feb 19, 2014 at 12:00 AM, Paul Moore p.f.moore@gmail.com wrote:
On 18 February 2014 12:49, MRAB python@mrabarnett.plus.com wrote:
I really think that this proposal needs to focus on the short, simple use cases and *not* worry about too much generality. For example:
sum(x[3] except 0 for x in list_of_tuples)
Shouldn't that be:
sum(x[3] except: 0 for x in list_of_tuples)
(colon added)?
Only if you feel that a colon is necessary for the syntax. I don't :-)
The colon is necessary if the new syntax should allow the specification of the exception. If it's always equivalent to a bare except that doesn't capture the exception object, then it'd be unnecessary.
Well, yes and no. There are only 2 out of the 10 syntaxes originally proposed in the PEP at the start of this thread that use a colon. I've already pointed out that I don't like a colon, so assume I said "except return 0" if you must :-)
Personally, I don't want to create a special syntax that does something that's strongly discouraged. I'm open to argument that the expression-except should accept just a single except clause; I'm open to argument that it shouldn't be able to capture (due to complications of creating sub-scopes), though that's more a nod to implementation/specification difficulty than any conceptual dislike of the syntax (it's clean, it's consistent, it's sometimes useful); but I don't want to narrow this down to always having to catch everything.
That's a fair point, certainly, and probably worth capturing explicitly in the PEP if it's not already there. I could argue that "bare except" is more acceptable in an *expression* context because expressions should never be so complex that the reasons why it's bad in a statement context actually apply. But I'd only be playing devil's advocate by doing so. My main point here was to force attention back onto the *real* use cases which (as I see it) are very simple exception handling for a simple subexpression embedded in a compound expression. Examples of how you could catch 3 different exceptions, use exception data, etc, are to my mind missing the point. At that stage, factor out the expression and use try/except *statements* and a temporary variable.
Also, "expr except fallback" is a very simple case of a keyword-based binary operation. So (ignoring the bare except issue) it's much less controversial. It's only when you need to specify the exception type that you get into ternary (and n-ary) territory, where there's a lot less consistency or prior art to base design decisions on. Hence all the multiple syntax proposals, fun debates and controversy ;-)
Paul
On Tue, Feb 18, 2014 at 9:07 AM, Paul Moore p.f.moore@gmail.com wrote:
Also, "expr except fallback" is a very simple case of a keyword-based binary operation.
I agree and ISTM a natural generalization of this to allow exception type specification would be
expr except(Exc1, Exc2) fallback
or
expr except[Exc1, Exc2] fallback
I am not sure if any of these have been proposed, so apologies if they were.
This said, I would still prefer the variant with the column as the closest to the existing syntax.
On Wed, Feb 19, 2014 at 1:27 AM, Alexander Belopolsky alexander.belopolsky@gmail.com wrote:
On Tue, Feb 18, 2014 at 9:07 AM, Paul Moore p.f.moore@gmail.com wrote:
Also, "expr except fallback" is a very simple case of a keyword-based binary operation.
I agree and ISTM a natural generalization of this to allow exception type specification would be
expr except(Exc1, Exc2) fallback
or
expr except[Exc1, Exc2] fallback
I am not sure if any of these have been proposed, so apologies if they were.
Interestingly, I hadn't read your post when I wrote my own suggestion of parentheses around the exception type. I'm still not convinced that it's an improvement over matching the statement form (it still encourages the bare-except usage, which should be discouraged), but the fact that two people have come up with this means it can go into the PEP.
ChrisA
On 2014-02-18 14:27, Alexander Belopolsky wrote:
On Tue, Feb 18, 2014 at 9:07 AM, Paul Moore <p.f.moore@gmail.com mailto:p.f.moore@gmail.com> wrote:
Also, "expr except fallback" is a very simple case of a keyword-based binary operation.
I agree and ISTM a natural generalization of this to allow exception type specification would be
expr except(Exc1, Exc2) fallback
Do you mean that the exception type would be optional? If so, then using parentheses would be a problem because the fallback itself could be in parentheses.
or
expr except[Exc1, Exc2] fallback
I am not sure if any of these have been proposed, so apologies if they were.
This said, I would still prefer the variant with the column as the closest to the existing syntax.
Another possibility would be to say that a bare except in an expression catches only certain "expression-oriented" exceptions, e.g. ValueError. The simplest way to do that would be to make them subclasses of an ExpressionError class. The question then becomes one of which exceptions are "expression-oriented"...
On Wed, Feb 19, 2014 at 2:13 AM, MRAB python@mrabarnett.plus.com wrote:
Another possibility would be to say that a bare except in an expression catches only certain "expression-oriented" exceptions, e.g. ValueError. The simplest way to do that would be to make them subclasses of an ExpressionError class. The question then becomes one of which exceptions are "expression-oriented"...
Easier than fiddling with class inheritance would be to make ExpressionError into a tuple of exception types - at least for testing purposes. If this _is_ to be done, I would definitely advocate having that name (or some equivalent) as a built-in, so it's straight-forward to either manipulate it or use it in a statement-except. But you're absolutely right that the question of which ones are expression-oriented would be a big one. I could imagine catching (ValueError, Unicode{En,De}codeError, AttributeError, EOFError, IOError, OSError, LookupError, NameError, ZeroDivisionError) and that's just from a quick skim of the built-in names. Would you consider making a tuple like that and then using "except CommonErrors:" all over your code? Or more to the point, if it were really convenient to do that, would it improve your code?
ChrisA
On 02/18/2014 07:25 AM, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 2:13 AM, MRAB python@mrabarnett.plus.com wrote:
Another possibility would be to say that a bare except in an expression catches only certain "expression-oriented" exceptions, e.g. ValueError. The simplest way to do that would be to make them subclasses of an ExpressionError class. The question then becomes one of which exceptions are "expression-oriented"...
Easier than fiddling with class inheritance would be to make ExpressionError into a tuple of exception types - at least for testing purposes. If this _is_ to be done, I would definitely advocate having that name (or some equivalent) as a built-in, so it's straight-forward to either manipulate it or use it in a statement-except. But you're absolutely right that the question of which ones are expression-oriented would be a big one. I could imagine catching (ValueError, Unicode{En,De}codeError, AttributeError, EOFError, IOError, OSError, LookupError, NameError, ZeroDivisionError) and that's just from a quick skim of the built-in names. Would you consider making a tuple like that and then using "except CommonErrors:" all over your code? Or more to the point, if it were really convenient to do that, would it improve your code?
If you're catching that many exceptions, you may as well just use a bare except. And be prepared to mask bugs.
-- ~Ethan~
On Wed, Feb 19, 2014 at 02:25:15AM +1100, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 2:13 AM, MRAB python@mrabarnett.plus.com wrote:
Another possibility would be to say that a bare except in an expression catches only certain "expression-oriented" exceptions, e.g. ValueError. The simplest way to do that would be to make them subclasses of an ExpressionError class. The question then becomes one of which exceptions are "expression-oriented"...
Easier than fiddling with class inheritance would be to make ExpressionError into a tuple of exception types - at least for testing purposes. If this _is_ to be done, I would definitely advocate having that name (or some equivalent) as a built-in, so it's straight-forward to either manipulate it or use it in a statement-except. But you're absolutely right that the question of which ones are expression-oriented would be a big one. I could imagine catching (ValueError, Unicode{En,De}codeError, AttributeError, EOFError, IOError, OSError, LookupError, NameError, ZeroDivisionError) and that's just from a quick skim of the built-in names. Would you consider making a tuple like that and then using "except CommonErrors:" all over your code? Or more to the point, if it were really convenient to do that, would it improve your code?
I hope that you were making a reductio ad absurdum argument as to why this is a bad idea.
Exception handlers should ALWAYS catch the FEWEST errors that you KNOW you need to handle. (Excuse my shouting, but this is important.) Encouraging people to catch all sorts of unrelated errors which, if they ever did occur, would absolutely definitely without a doubt represent an actual bug in their code is a terrible, terrible idea.
If you wrote
1/x except CommonErrors: 0
expecting that x was a number, and IOError was raised, caught and swallowed, wouldn't you rather know about it?
On Wed, Feb 19, 2014 at 10:34 AM, Steven D'Aprano steve@pearwood.info wrote:
I could imagine catching (ValueError, Unicode{En,De}codeError, AttributeError, EOFError, IOError, OSError, LookupError, NameError, ZeroDivisionError) and that's just from a quick skim of the built-in names. Would you consider making a tuple like that and then using "except CommonErrors:" all over your code? Or more to the point, if it were really convenient to do that, would it improve your code?
I hope that you were making a reductio ad absurdum argument as to why this is a bad idea.
Exception handlers should ALWAYS catch the FEWEST errors that you KNOW you need to handle. (Excuse my shouting, but this is important.)
Pretty much. I started out saying that it doesn't need to be a superclass (thus there's no need to "infect" every exception definition with this check), but by the time I'd written out that list of stuff that would make sense to catch, it was pretty obvious that such a diverse list would make a terrible default.
Also, compare this list against the first 125 lines of my stdlib conversion examples (the "easy ones", the mostly-non-controversial ones). Even ignoring the ones that are too broad for their needs, there are some that legitimately catch Exception, TypeError, StopIteration, and a custom error from the email module (which would be reasonably well handled by a subclassing solution but not really with the tuple). There's no way to restrict this without making it simultaneously dangerous AND useless.
ChrisA
On Tue, Feb 18, 2014 at 10:13 AM, MRAB python@mrabarnett.plus.com wrote:
Another possibility would be to say that a bare except in an expression catches only certain "expression-oriented" exceptions, e.g. ValueError. The simplest way to do that would be to make them subclasses of an ExpressionError class.
+1
The question then becomes one of which exceptions are "expression-oriented"...
Here is my attempt to answer:
- ArithmeticError - AttributeError - LookupError - TypeError ? - ValueError ?
On 18/02/2014 15:26, Alexander Belopolsky wrote:
On Tue, Feb 18, 2014 at 10:13 AM, MRAB <python@mrabarnett.plus.com mailto:python@mrabarnett.plus.com> wrote:
Another possibility would be to say that a bare except in an expression catches only certain "expression-oriented" exceptions, e.g. ValueError. The simplest way to do that would be to make them subclasses of an ExpressionError class.
+1
The question then becomes one of which exceptions are "expression-oriented"...
Here is my attempt to answer:
- ArithmeticError
- AttributeError
- LookupError
- TypeError ?
- ValueError ?
Sorry, but this seems to me to be a futile discussion. Expressions can contain functions. Functions can raise any exception they like. So evaluating an expression can raise any exception you care to name. Rob Cliffe
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
No virus found in this message. Checked by AVG - www.avg.com http://www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6602 - Release Date: 02/17/14
On Wed, Feb 19, 2014 at 2:30 AM, Rob Cliffe rob.cliffe@btinternet.com wrote:
Sorry, but this seems to me to be a futile discussion. Expressions can contain functions. Functions can raise any exception they like. So evaluating an expression can raise any exception you care to name.
Of course it could raise anything, but what are you likely to want to catch and handle by returning a different value?
LookupError when your cache isn't populated UnicodeDecodeError to fall back on a different encoding ("try UTF-8; if that fails, assume the other end is Windows and try CP-1252" - I have that logic in some places) ZeroDivisionError and return float("inf"), float("nan"), or 0
But you wouldn't want to catch:
SyntaxError ImportError AssertionError IndentationError ProgrammerIsAMoronAndHisCodeIsInError
because most of them wouldn't come up in an expression context, and if they do, you aren't likely to have a useful alternate value to return.
So it is conceivable that there be some tuple or superclass of "common errors worth catching". Trouble is, it still often wouldn't be appropriate to catch them all - maybe you want to hook into LookupError, but accidentally catching a UnicodeDecodeError along the way would mask a bug.
ChrisA
On 2014-02-18 15:40, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 2:30 AM, Rob Cliffe rob.cliffe@btinternet.com wrote:
Sorry, but this seems to me to be a futile discussion. Expressions can contain functions. Functions can raise any exception they like. So evaluating an expression can raise any exception you care to name.
Of course it could raise anything, but what are you likely to want to catch and handle by returning a different value?
LookupError when your cache isn't populated UnicodeDecodeError to fall back on a different encoding ("try UTF-8; if that fails, assume the other end is Windows and try CP-1252" - I have that logic in some places) ZeroDivisionError and return float("inf"), float("nan"), or 0
But you wouldn't want to catch:
SyntaxError ImportError AssertionError IndentationError ProgrammerIsAMoronAndHisCodeIsInError
I think that last one should be:
ProgrammerIsAMoronAndHisCodeIsInErrorError
or just:
PebkacError
because most of them wouldn't come up in an expression context, and if they do, you aren't likely to have a useful alternate value to return.
So it is conceivable that there be some tuple or superclass of "common errors worth catching". Trouble is, it still often wouldn't be appropriate to catch them all - maybe you want to hook into LookupError, but accidentally catching a UnicodeDecodeError along the way would mask a bug.
On Wed, Feb 19, 2014 at 3:01 AM, MRAB python@mrabarnett.plus.com wrote:
ProgrammerIsAMoronAndHisCodeIsInError
I think that last one should be:
ProgrammerIsAMoronAndHisCodeIsInErrorError
or just:
PebkacError
The error is that his code is in. If his code had been out, that would have been fine. When this exception is thrown, you need to hit it with your bat, otherwise your code will be out as soon as the exception hits the stumps.
ChrisA
On Tue, Feb 18, 2014 at 10:30 AM, Rob Cliffe rob.cliffe@btinternet.com wrote:
Sorry, but this seems to me to be a futile discussion. Expressions can contain functions. Functions can raise any exception they like. So evaluating an expression can raise any exception you care to name.
This is true, but it does not follow that you need to be able to catch "any exception you care to name" in the expression.
Exceptions that are caused by conditions not obvious from the expression are better handled at a higher level.
Let's focus on the main use cases. E.g. a better spelling for
x = d.get(key, default) -> x = d[key] except default try: x = 1/y except ArithmeticError: x=0; -> x = 1/y except 0
etc.
I would not mind
x = 1/f(y) except 0
not catching a TypeError that may come from f(y) in most cases.
On Wed, Feb 19, 2014 at 2:41 AM, Alexander Belopolsky alexander.belopolsky@gmail.com wrote:
I would not mind
x = 1/f(y) except 0
not catching a TypeError that may come from f(y) in most cases.
But should it catch a KeyError from inside f(y), based on the translation of d.get()?
ChrisA
On 2014-02-18 15:51, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 2:41 AM, Alexander Belopolsky alexander.belopolsky@gmail.com wrote:
I would not mind
x = 1/f(y) except 0
not catching a TypeError that may come from f(y) in most cases.
But should it catch a KeyError from inside f(y), based on the translation of d.get()?
The current code would use try...except. Would that catch a KeyError from inside f(y)? Yes.
The problem is that sometimes you want to catch only ZeroDivisionError, so we need to be able to specify the exception.
The question is whether it should be OK to allow a bare except, where that would catch a limited number of exceptions, in those cases where there won't be any risk.
On Wed, Feb 19, 2014 at 3:09 AM, MRAB python@mrabarnett.plus.com wrote:
The question is whether it should be OK to allow a bare except, where that would catch a limited number of exceptions, in those cases where there won't be any risk.
Yeah. I don't like the idea that omitting the exception name(s) would have a completely different effect in expression or statement form. Compare:
value = true_value if condition else false_value # <-> if condition: value = true_value else: value = false_value
func = lambda x: x+4 # <-> def func(x): return x+4
lst = [x*x for x in range(5)] # <-> lst = [] for x in range(5): lst.append(x*x)
Each of them has a "corresponding statement form" that's more-or-less the same in effect (little stuff like name leakage aside), and which is spelled very similarly. You wouldn't expect, for instance, lambda functions to specify their args in reverse order compared to def functions. So if it's syntactically legal to have a bare except in an expression (still under debate), then that bare except should be equivalent to "except BaseException" - not "except Exception", not "except DWIMException", not anything else. Otherwise it'd just cause confusion when trying to decode a complex expression.
ChrisA
On Tue, Feb 18, 2014 at 04:09:14PM +0000, MRAB wrote:
The question is whether it should be OK to allow a bare except, where that would catch a limited number of exceptions, in those cases where there won't be any risk.
Having a bare except catch everything in a try...except block, and only a few things in an except expression, is a violation of the Principle of Least Surprise. Not to mention the Zen about implicit vs explicit.
And the idea that the language designers -- that's us -- might be able to predict ahead of time which exceptions aren't risky is laughable. We really can't. ANY exception could be a bug in the code, and therefore wrong to mask by default. The only person capable of correctly deciding which exceptions to catch is the person writing the code.
(And not even always them, but we don't have to solve the problem of poor coders writing poor code. It's enough to avoid encouraging it.)
On 18 February 2014 15:51, Chris Angelico rosuav@gmail.com wrote:
On Wed, Feb 19, 2014 at 2:41 AM, Alexander Belopolsky alexander.belopolsky@gmail.com wrote:
I would not mind
x = 1/f(y) except 0
not catching a TypeError that may come from f(y) in most cases.
But should it catch a KeyError from inside f(y), based on the translation of d.get()?
Explicit is better than implicit - I think this discussion has done its job and established that trying to assume a subset of exceptions to catch isn't going to work. We either allow a bare except to mean "catch all exceptions" (which exactly the same risks and provisos as a bare except *statement*) or we make the exception to catch mandatory.
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
Paul
On Wed, Feb 19, 2014 at 3:25 AM, Paul Moore p.f.moore@gmail.com wrote:
Explicit is better than implicit - I think this discussion has done its job and established that trying to assume a subset of exceptions to catch isn't going to work. We either allow a bare except to mean "catch all exceptions" (which exactly the same risks and provisos as a bare except *statement*) or we make the exception to catch mandatory.
Yep, agreed. I'm personally inclined to permit the bare except, and then advise against it in PEP 8, but both halves of that are debatable. I'm of the opinion that this would be risky:
lst[x] except: "x is out of bounds"
and would prefer the more verbose:
lst[x] except KeyError: "x is out of bounds"
but I can understand that some people will prefer the shorter form, even though it could mask a typo in either name. It's no different from any other uncatchable error:
if html_tag == "<scrpit>": handle_script()
Nobody expects the Python inquisition to catch that for them. A bare except lets you sacrifice some quick-error-catching-ness for some quick-code-typing-ness. [1]
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
Hmm. Would that make anything any clearer? It feels like the sorts of crazy limitations that I've seen in some other languages, like how PHP up until relatively recently wouldn't let you subscript an array returned from a function without first assigning it to a variable:
$x = func()[5];
$x = func(); $x = $x[5];
One of the things I like about Python is that anything is itself, regardless of its context. An expression yields a value, that value can be stored, and any expression yielding the same value will be exactly the same thing:
func = obj.method func() # <-> obj.method()
Contrast JavaScript, where those two are actually different.
So in exception catching, *especially* in an expression context (where you can't assign anyway), is it really necessary to dump your two-name tuple out into a temporary name?
[1] Now my fingers are wondering: Is there a global-interpreter-loch-ness monster?
On 18 February 2014 16:43, Chris Angelico rosuav@gmail.com wrote:
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
Hmm. Would that make anything any clearer? It feels like the sorts of crazy limitations that I've seen in some other languages, like how PHP up until relatively recently wouldn't let you subscript an array returned from a function without first assigning it to a variable:
Maybe not. Maybe again it's just a matter of a style recommendation. But the PEP itself has to tread a fine line between showing what is *possible* vs showing what is *intended* - I feel that the intention of the except construct should *not* be to do most of the crazy things people are talking about in this thread.
Paul
On Wed, Feb 19, 2014 at 3:56 AM, Paul Moore p.f.moore@gmail.com wrote:
On 18 February 2014 16:43, Chris Angelico rosuav@gmail.com wrote:
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
Hmm. Would that make anything any clearer? It feels like the sorts of crazy limitations that I've seen in some other languages, like how PHP up until relatively recently wouldn't let you subscript an array returned from a function without first assigning it to a variable:
Maybe not. Maybe again it's just a matter of a style recommendation. But the PEP itself has to tread a fine line between showing what is *possible* vs showing what is *intended* - I feel that the intention of the except construct should *not* be to do most of the crazy things people are talking about in this thread.
That's fair. It's tricky to show that this really would be an improvement to the language when it's showing very little, but tricky to show that it'd be an improvement when the expressions are horrendously obfuscated.
I expect that this would be used mostly in really really simple ways.
ChrisA
On 18/02/2014 16:56, Paul Moore wrote:
On 18 February 2014 16:43, Chris Angelico rosuav@gmail.com wrote:
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
Hmm. Would that make anything any clearer? It feels like the sorts of crazy limitations that I've seen in some other languages, like how PHP up until relatively recently wouldn't let you subscript an array returned from a function without first assigning it to a variable:
Maybe not. Maybe again it's just a matter of a style recommendation. But the PEP itself has to tread a fine line between showing what is *possible* vs showing what is *intended* - I feel that the intention of the except construct should *not* be to do most of the crazy things people are talking about in this thread.
Almost any language feature can be abused. That doesn't mean we should regulate their use. Rob Cliffe
Paul _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
No virus found in this message. Checked by AVG - www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6602 - Release Date: 02/17/14
-1 to any proposal that a bare except in an expression should catch a different set of exceptions than a bare except in a statement. That's incredibly unintuitive.
On Tue Feb 18 2014 at 10:37:24 AM, Rob Cliffe rob.cliffe@btinternet.com wrote:
On 18/02/2014 16:56, Paul Moore wrote:
On 18 February 2014 16:43, Chris Angelico rosuav@gmail.com wrote:
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
Hmm. Would that make anything any clearer? It feels like the sorts of crazy limitations that I've seen in some other languages, like how PHP up until relatively recently wouldn't let you subscript an array returned from a function without first assigning it to a variable:
Maybe not. Maybe again it's just a matter of a style recommendation. But the PEP itself has to tread a fine line between showing what is *possible* vs showing what is *intended* - I feel that the intention of the except construct should *not* be to do most of the crazy things people are talking about in this thread.
Almost any language feature can be abused. That doesn't mean we should regulate their use. Rob Cliffe
Paul _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
No virus found in this message. Checked by AVG - www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6602 - Release Date: 02/17/14
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 18/02/2014 16:43, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 3:25 AM, Paul Moore p.f.moore@gmail.com wrote:
Explicit is better than implicit - I think this discussion has done its job and established that trying to assume a subset of exceptions to catch isn't going to work. We either allow a bare except to mean "catch all exceptions" (which exactly the same risks and provisos as a bare except *statement*) or we make the exception to catch mandatory.
Yep, agreed. I'm personally inclined to permit the bare except, and then advise against it in PEP 8, but both halves of that are debatable. I'm of the opinion that this would be risky:
lst[x] except: "x is out of bounds"
and would prefer the more verbose:
lst[x] except KeyError: "x is out of bounds"
but I can understand that some people will prefer the shorter form, even though it could mask a typo in either name. It's no different from any other uncatchable error:
if html_tag == "<scrpit>": handle_script()
Nobody expects the Python inquisition to catch that for them. A bare except lets you sacrifice some quick-error-catching-ness for some quick-code-typing-ness. [1]
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
Hmm. Would that make anything any clearer? It feels like the sorts of crazy limitations that I've seen in some other languages, like how PHP up until relatively recently wouldn't let you subscript an array returned from a function without first assigning it to a variable:
$x = func()[5];
$x = func(); $x = $x[5];
One of the things I like about Python is that anything is itself, regardless of its context. An expression yields a value, that value can be stored, and any expression yielding the same value will be exactly the same thing:
func = obj.method func() # <-> obj.method()
As anyone who has followed my contributions can probably guess, you're preaching to the converted here. Rob Cliffe
Contrast JavaScript, where those two are actually different.
So in exception catching, *especially* in an expression context (where you can't assign anyway), is it really necessary to dump your two-name tuple out into a temporary name?
[1] Now my fingers are wondering: Is there a global-interpreter-loch-ness monster? _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
No virus found in this message. Checked by AVG - www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6602 - Release Date: 02/17/14
18.02.2014 17:43, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 3:25 AM, Paul Moore p.f.moore@gmail.com wrote:
Explicit is better than implicit - I think this discussion has done its job and established that trying to assume a subset of exceptions to catch isn't going to work. We either allow a bare except to mean "catch all exceptions" (which exactly the same risks and provisos as a bare except *statement*) or we make the exception to catch mandatory.
Yep, agreed. I'm personally inclined to permit the bare except, and then advise against it in PEP 8, but both halves of that are debatable. I'm of the opinion that this would be risky:
IMHO bare except is practically always a very bad practice unless the exception is immediately re-raised:
try: foo() except: logger.exception('error:') raise
...and AFAIK, up to now, nobody proposed any syntax that would make it possible to re-raise an exception in an except expression.
Therefore I believe that bare except should *not* be allowed in except expressions at all.
My-3-cents-ly yours *j
On Wed, Feb 19, 2014 at 11:11 AM, Jan Kaliszewski zuo@chopin.edu.pl wrote:
IMHO bare except is practically always a very bad practice unless the exception is immediately re-raised:
try: foo() except: logger.exception('error:') raise
...and AFAIK, up to now, nobody proposed any syntax that would make it possible to re-raise an exception in an except expression.
Therefore I believe that bare except should *not* be allowed in except expressions at all.
Reraising inside an expression doesn't make a huge amount of sense. If you want to do something and then reraise, what's the value of the expression? Go statement-form and make it clearer.
But there are legit uses of broad except. One is to use the exception itself as the value. (See Lib/imaplib.py:568, quoted in the examples.) Another is for a "this must never fail" code like repr (Lib/asyncore.py:482, the next example). Arguably both should be catching Exception, not BaseException; but the recommendation to "log it and reraise" should apply just as much to catching Exception as to bare-excepting, as an unexpected TypeError or AttributeError could indicate a significant bug.
IMO the validity of bare except in an expression should be based on readability and bug-magnet possibility, not whether or not the exception can be reraised.
ChrisA
19.02.2014 02:08, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 11:11 AM, Jan Kaliszewski zuo@chopin.edu.pl wrote:
IMHO bare except is practically always a very bad practice unless the exception is immediately re-raised:
try: foo() except: logger.exception('error:') raise
...and AFAIK, up to now, nobody proposed any syntax that would make it possible to re-raise an exception in an except expression.
Therefore I believe that bare except should *not* be allowed in except expressions at all.
Reraising inside an expression doesn't make a huge amount of sense. If you want to do something and then reraise, what's the value of the expression? Go statement-form and make it clearer.
Exactly. That's why I believe bare except should be disallowed in the expression form.
But there are legit uses of broad except.
Apart from immediately-re-raising, base except are practically never a legit use IMHO.
One is to use the exception itself as the value. (See Lib/imaplib.py:568, quoted in the examples.) Another is for a "this must never fail" code like repr (Lib/asyncore.py:482, the next example). Arguably both should be catching Exception, not BaseException;
I believe they should indeed.
but the recommendation to "log it and reraise" should apply just as much to catching Exception as to bare-excepting, as an unexpected TypeError or AttributeError could indicate a significant bug.
"Log it" -- yes. "Reraise" -- not necessarily for Exception-based ones, if the are logged, IMHO (see sources of logging, asyncio, tornado...).
IMO the validity of bare except in an expression should be based on readability and bug-magnet possibility, not whether or not the exception can be reraised.
Bare except statement is indeed a bug magnet, but:
* it's a legacy that cannot be removed from the language easily,
* at least there are cases when it is valid and also seems to be somewhat elegant:
except: # *bare* except ... raise # *bare* raise
On the other hand, bare except expression would be a bug magnet, without any legit use cases on horizon. And, I suppose, the magnet would be even stronger -- expecially for keystroke-saving-lovers. :)
Let's do not introduce an attractive nuisance whose positive value is near zero.
Cheers. *j
On 2014-02-19 01:37, Jan Kaliszewski wrote:
19.02.2014 02:08, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 11:11 AM, Jan Kaliszewski zuo@chopin.edu.pl wrote:
IMHO bare except is practically always a very bad practice unless the exception is immediately re-raised:
try: foo() except: logger.exception('error:') raise
...and AFAIK, up to now, nobody proposed any syntax that would make it possible to re-raise an exception in an except expression.
Therefore I believe that bare except should *not* be allowed in except expressions at all.
Reraising inside an expression doesn't make a huge amount of sense. If you want to do something and then reraise, what's the value of the expression? Go statement-form and make it clearer.
Exactly. That's why I believe bare except should be disallowed in the expression form.
But there are legit uses of broad except.
Apart from immediately-re-raising, base except are practically never a legit use IMHO.
+1
On the one hand, allowing a bare except would be consistent with the statement form.
On the other hand, without the ability to re-raise, it's just asking for trouble, although there _is_ that "consenting adults" thing! :-)
On Wed, Feb 19, 2014 at 1:55 PM, MRAB python@mrabarnett.plus.com wrote:
On the one hand, allowing a bare except would be consistent with the statement form.
On the other hand, without the ability to re-raise, it's just asking for trouble, although there _is_ that "consenting adults" thing! :-)
Regardless of the ability to re-raise, I wouldn't be against disallowing a bare except, and insisting that it be spelled "except BaseException:" instead. The main argument against that is consistency; in fact, possibly the *only* viable argument against that. Obviously backward compatibility is a strong reason to keep support in the statement form, but how important is it to be consistent with something that's strongly discouraged anyway?
Hmm. Actually, how strongly *is* the bare except discouraged? There are a good lot of them in the stdlib, and quite a few probably should be "except Exception" anyway. Currently, PEP 8 permits two use-cases (log and continue, and clean-up and reraise), but then maybe discourages one of them. Core devs, what's your opinion on new code with "except:" in it? Would you prefer to see it spelled "except BaseException:"?
ChrisA
On Tue, Feb 18, 2014 at 11:25 AM, Paul Moore p.f.moore@gmail.com wrote:
On 18 February 2014 15:51, Chris Angelico rosuav@gmail.com wrote:
On Wed, Feb 19, 2014 at 2:41 AM, Alexander Belopolsky alexander.belopolsky@gmail.com wrote:
I would not mind
x = 1/f(y) except 0
not catching a TypeError that may come from f(y) in most cases.
But should it catch a KeyError from inside f(y), based on the translation of d.get()?
Explicit is better than implicit - I think this discussion has done its job and established that trying to assume a subset of exceptions to catch isn't going to work. We either allow a bare except to mean "catch all exceptions" (which exactly the same risks and provisos as a bare except *statement*) or we make the exception to catch mandatory.
I disagree. It is best to leave explicit selection of exceptions to catch outside of the expressions. This is job for things like decimal or numpy fp contexts.
Always requiring a long camel-case name inside an expression will kill most of the advantages.
For example, it would be nice to be able to write something like
x = d1[key] except d2[key] except 0
but sprinkle this with repeated KeyError and what is important (d1 and d2) will be lost in the scaffolding.
On 18/02/2014 17:01, Alexander Belopolsky wrote:
On Tue, Feb 18, 2014 at 11:25 AM, Paul Moore <p.f.moore@gmail.com mailto:p.f.moore@gmail.com> wrote:
On 18 February 2014 15:51, Chris Angelico <rosuav@gmail.com <mailto:rosuav@gmail.com>> wrote: > On Wed, Feb 19, 2014 at 2:41 AM, Alexander Belopolsky > <alexander.belopolsky@gmail.com <mailto:alexander.belopolsky@gmail.com>> wrote: >> I would not mind >> >> x = 1/f(y) except 0 >> >> not catching a TypeError that may come from f(y) in most cases. > > But should it catch a KeyError from inside f(y), based on the > translation of d.get()? Explicit is better than implicit - I think this discussion has done its job and established that trying to assume a subset of exceptions to catch isn't going to work. We either allow a bare except to mean "catch all exceptions" (which exactly the same risks and provisos as a bare except *statement*) or we make the exception to catch mandatory.
I disagree. It is best to leave explicit selection of exceptions to catch outside of the expressions. This is job for things like decimal or numpy fp contexts.
Always requiring a long camel-case name inside an expression will kill most of the advantages.
For example, it would be nice to be able to write something like
x = d1[key] except d2[key] except 0
but sprinkle this with repeated KeyError and what is important (d1 and d2) will be lost in the scaffolding.
Hm. Currently you can write
x = d1.get(key, d2.get(key, 0))
which is admirably concise, and perhaps you like it. Personally I don't find it particularly readable (and it is one of the API complications this proposal aims to make unnecessary).
If we move on from that, this proposal means that, even with sprinkling keywords you can replace:
try: x = d1[key] except KeyError: try: x = d2[key] except KeyError: x = 0
with:
x = d1[key] except KeyError: (d2[key] except KeyError: 0)
Again this is subjective, but I find that a significant gain. 7 lines replaced by 1 not terribly long line. Not many keystrokes saved, true. But no indents to type, and no visual jumping around the indents when reading it. The new version is dense, yes, but I find it readable (Westerners find it natural to read left-to-right), explicit, and with every bit of it except the colons meaningful. Perhaps to be fair the short variable names "d1" and "d2" are a bit swamped by the longer exception name "KeyError".
To digress somewhat:
I typed in the parentheses to convey my intention without analysing whether they were necessary. I don't think they are because, as far as I can see, the alternative reading
x = (d1[key] except KeyError: d2[key]) except KeyError: 0
would have exactly the same semantics, and still would do if "d1" and "d2" were replaced by more complicated expressions which themselves might raise a KeyError (which I find kind of reassuring, it means we don't have to decide a precedence of one "except" over another). (Oops, now I seem to be almost arguing for the "d1.get(..." form which would propagate errors evaluating "d1" or "d2".) But if I am missing something, I'd be interested.
Rob Cliffe
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
No virus found in this message. Checked by AVG - www.avg.com http://www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6602 - Release Date: 02/17/14
Alexander Belopolsky wrote:
For example, it would be nice to be able to write something like
x = d1[key] except d2[key] except 0
but sprinkle this with repeated KeyError and what is important (d1 and d2) will be lost in the scaffolding.
Another crazy thought: Make the default exception depend on what kind of expression the except clause is attached to.
things[i] except: default # catches LookupError
distance / time except: very_fast # catches ZeroDivisionError
Anything else would require an explicit exception type.
On Tue, Feb 18, 2014 at 12:01:29PM -0500, Alexander Belopolsky wrote:
I disagree. It is best to leave explicit selection of exceptions to catch outside of the expressions. This is job for things like decimal or numpy fp contexts.
I don't understand what relevance fp contexts have here.
Always requiring a long camel-case name inside an expression will kill most of the advantages.
I doubt that. And they're not typically that long. TypeError is nine characters, no longer than your name :-)
For example, it would be nice to be able to write something like
x = d1[key] except d2[key] except 0
Which is harmful, because it masks bugs. If you want to save typing, define K = KeyError at the top of your module, and write:
x = d1[key] except K d2[key] except K 0
which by the way looks horrible without the colons (sorry Paul, you have not convinced me that the Tim Peters prohibition on grit applies here). Even with three levels of indentation, mandatory brackets, and colons, it still fits within 79 columns and reads quite well:
if condition: while something(): for key in sequence: x = (d1[key] except KeyError: (d2[key] except KeyError: 0))
Take the colons and brackets out, and I think it is less readable:
x = d1[key] except KeyError d2[key] except KeyError 0
and ambiguous.
By the way, I think this is a good example for the PEP (Chris are you reading?). You might be tempted to re-write this as:
x = d1.get(key, d2.get(key, 0))
which is shorter, but the semantics are different. If the second case, you have to pre-calculate the fallback, which may be expensive, while in the exception form, you only calculate the fallback if the first lookup actually fails.
but sprinkle this with repeated KeyError and what is important (d1 and d2) will be lost in the scaffolding.
I think that is wrong. I realise it is a matter of taste, but in this instance I think my taste is much closer to the rest of Python's syntax than yours is. (But you probably think the same, yes?)
19.02.2014 01:10, Steven D'Aprano wrote:
On Tue, Feb 18, 2014 at 12:01:29PM -0500, Alexander Belopolsky wrote:
I disagree. It is best to leave explicit selection of exceptions to catch outside of the expressions. This is job for things like decimal or numpy fp contexts.
I don't understand what relevance fp contexts have here.
Always requiring a long camel-case name inside an expression will kill most of the advantages.
I doubt that. And they're not typically that long. TypeError is nine characters, no longer than your name :-)
For example, it would be nice to be able to write something like
x = d1[key] except d2[key] except 0
Which is harmful, because it masks bugs. If you want to save typing, define K = KeyError at the top of your module, and write:
x = d1[key] except K d2[key] except K 0
which by the way looks horrible without the colons (sorry Paul, you have not convinced me that the Tim Peters prohibition on grit applies here). Even with three levels of indentation, mandatory brackets, and colons, it still fits within 79 columns and reads quite well:
if condition: while something(): for key in sequence: x = (d1[key] except KeyError: (d2[key] except KeyError: 0))
Take the colons and brackets out, and I think it is less readable:
x = d1[key] except KeyError d2[key] except KeyError 0
and ambiguous.
+1.
Though I still like the paren-after-except syntax more. :)
x = d1[key] except (KeyError: d2[key] except (KeyError: 0))
An advantage of this one is IMHO that:
1. first we see the basic expression (d1[key]) 2. but, immediately, we notice the 'except' keyword ("a-ha: generally it is *d1[key]* but with reservations for some special cases, let's see them...") 3. then, we read what are those "special cases" (KeyError: ...) which visually are nicely separated from the basic expression.
Cheers. *j
PS. Of course it could be laid out with some line breaks, e.g.:
x = (d1[key] except (KeyError: d2[key] except (KeyError: 0)))
PS2. Yes, I also see some visual advantages of the paren-enclosing-whole-expression syntax when applying that kind of layout:
x = (d1[key] except KeyError: (d2[key] except KeyError: 0))
PS3. Anyway, IMHO *obligatory* parens would be a good thing. Also because they "neutralize" the visual connotations of colon with its typical role of block-statement indicator (as it's obvious that a block cannot start within parens).
On Wed, Feb 19, 2014 at 11:10 AM, Steven D'Aprano steve@pearwood.info wrote:
for key in sequence: x = (d1[key] except KeyError: (d2[key] except KeyError: 0))
Take the colons and brackets out, and I think it is less readable:
x = d1[key] except KeyError d2[key] except KeyError 0
and ambiguous.
By the way, I think this is a good example for the PEP (Chris are you reading?). You might be tempted to re-write this as:
x = d1.get(key, d2.get(key, 0))
which is shorter, but the semantics are different. If the second case, you have to pre-calculate the fallback, which may be expensive, while in the exception form, you only calculate the fallback if the first lookup actually fails.
I certainly am reading :) Going a bit further by having the final fall-back be a function call (highlighting the fact that it may be expensive).
""" Consider this example of a two-level cache:: for key in sequence: x = (lvl1[key] except KeyError: (lvl2[key] except KeyError: f(key)))
This cannot be rewritten as:: x = lvl1.get(key, lvl2.get(key, f(key)))
which, despite being shorter, defeats the purpose of the cache, as it must calculate a default value to pass to get(). The .get() version calculates backwards; the exception-testing version calculates forwards, as would be expected. """
ChrisA
On 19/02/2014 01:00, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 11:10 AM, Steven D'Aprano steve@pearwood.info wrote:
for key in sequence: x = (d1[key] except KeyError: (d2[key] except KeyError: 0))
Take the colons and brackets out, and I think it is less readable:
x = d1[key] except KeyError d2[key] except KeyError 0
and ambiguous.
By the way, I think this is a good example for the PEP (Chris are you reading?). You might be tempted to re-write this as:
x = d1.get(key, d2.get(key, 0))
which is shorter, but the semantics are different. If the second case, you have to pre-calculate the fallback, which may be expensive, while in the exception form, you only calculate the fallback if the first lookup actually fails.
Yo! I actually mentioned this way of rewriting it, but I missed that the semantics were different and more expensive. Long live this proposal! (Although to carp, I think that the colons are desirable but the brackets are not.)
I certainly am reading :) Going a bit further by having the final fall-back be a function call (highlighting the fact that it may be expensive).
""" Consider this example of a two-level cache:: for key in sequence: x = (lvl1[key] except KeyError: (lvl2[key] except KeyError: f(key)))
This cannot be rewritten as:: x = lvl1.get(key, lvl2.get(key, f(key)))
which, despite being shorter, defeats the purpose of the cache, as it must calculate a default value to pass to get(). The .get() version calculates backwards; the exception-testing version calculates forwards, as would be expected. """
ChrisA
Again, yo! Surely we are on to a good thing here with this proposal? Rob Cliffe
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
No virus found in this message. Checked by AVG - www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6604 - Release Date: 02/18/14
On Tue, Feb 18, 2014 at 7:10 PM, Steven D'Aprano steve@pearwood.infowrote:
I disagree. It is best to leave explicit selection of exceptions to
catch
outside of the expressions. This is job for things like decimal or numpy fp contexts.
I don't understand what relevance fp contexts have here.
numpy.seterr(divide='ignore')
{'over': 'warn', 'divide': 'raise', 'invalid': 'warn', 'under': 'ignore'}
1/numpy.array(0.0)
inf
numpy.seterr(divide='raise')
{'over': 'warn', 'divide': 'ignore', 'invalid': 'warn', 'under': 'ignore'}
1/numpy.array(0.0)
Traceback (most recent call last): File "<stdin>", line 1, in <module> FloatingPointError: divide by zero encountered in divide
On Wed, Feb 19, 2014 at 12:34 PM, Alexander Belopolsky alexander.belopolsky@gmail.com wrote:
On Tue, Feb 18, 2014 at 7:10 PM, Steven D'Aprano steve@pearwood.info wrote:
I disagree. It is best to leave explicit selection of exceptions to catch outside of the expressions. This is job for things like decimal or numpy fp contexts.
I don't understand what relevance fp contexts have here.
numpy.seterr(divide='ignore')
{'over': 'warn', 'divide': 'raise', 'invalid': 'warn', 'under': 'ignore'}
1/numpy.array(0.0)
inf
numpy.seterr(divide='raise')
{'over': 'warn', 'divide': 'ignore', 'invalid': 'warn', 'under': 'ignore'}
1/numpy.array(0.0)
Traceback (most recent call last): File "<stdin>", line 1, in <module> FloatingPointError: divide by zero encountered in divide
That's solving the same problem in a slightly different way. Instead of quickly trapping one exception right here, you set a state flag that controls them globally. In a computationally-heavy program, that's going to work out far FAR cleaner than this; but suppose most of the time you want them to raise, and then you have one little calculation in the middle where you don't. That's where this would come in handy.
ChrisA
On 02/18/2014 05:34 PM, Alexander Belopolsky wrote:
On Tue, Feb 18, 2014 at 7:10 PM, Steven D'Aprano wrote:
Alexander wrote:
I disagree. It is best to leave explicit selection of exceptions to catch outside of the expressions. This is job for things like decimal or numpy fp contexts.
I don't understand what relevance fp contexts have here.
[snip nice examples of fp context]
Which is great for numpy et al, but useless for the rest of the Python world.
-- ~Ethan~
On 19 February 2014 00:10, Steven D'Aprano steve@pearwood.info wrote:
x = d1[key] except K d2[key] except K 0
which by the way looks horrible without the colons (sorry Paul, you have not convinced me that the Tim Peters prohibition on grit applies here).
While I did say later on yesterday that I'd become less uncomfortable with the except Whatever: form, I was never advocating just removing the colons - pick your favourite keyword-based proposal and compare with that if you want to see what I was talking about.
But that ship has sailed, for better or worse, and it looks like the except-colon form is the winner. So be it.
Paul
On Wed, Feb 19, 2014 at 6:25 PM, Paul Moore p.f.moore@gmail.com wrote:
On 19 February 2014 00:10, Steven D'Aprano steve@pearwood.info wrote:
x = d1[key] except K d2[key] except K 0
which by the way looks horrible without the colons (sorry Paul, you have not convinced me that the Tim Peters prohibition on grit applies here).
While I did say later on yesterday that I'd become less uncomfortable with the except Whatever: form, I was never advocating just removing the colons - pick your favourite keyword-based proposal and compare with that if you want to see what I was talking about.
But that ship has sailed, for better or worse, and it looks like the except-colon form is the winner. So be it.
The except-colon form is *my* currently-preferred spelling. But when Guido took a quick look at it, he didn't like the colon form, and his preference makes a lot more difference than mine :) It remains to be seen whether the PEP convinces him one way or the other.
Have you a preferred keyword-based proposal? Does it use an existing keyword or create a new one? The floor is yours, Paul: sell me your keyword :) Bear in mind, I used to be in favour of the "expr except Exception pass default" form, so it shouldn't be too hard to push me back to there.
ChrisA
On 19 February 2014 07:34, Chris Angelico rosuav@gmail.com wrote:
Have you a preferred keyword-based proposal? Does it use an existing keyword or create a new one? The floor is yours, Paul: sell me your keyword :) Bear in mind, I used to be in favour of the "expr except Exception pass default" form, so it shouldn't be too hard to push me back to there.
Honestly? No, I don't. If I need an example of non-colon syntax I choose "return" and consider "pass" because those are the 2 that seem most reasonable, and I remember "return" fastest. But I can't really argue strongly for them - whoever said it was right, *all* of the keyword approaches are picking "something that's not awful" from what we have.
My list of new keywords is basically the same as yours, but I don't think the construct is important enough to warrant a new keyword (if the if-expression couldn't justify "then", then we don't stand a chance!)
So having struggled to find objections to the colon syntax, I've reached a point where I think it's the best of the alternatives. "I can't find a good enough objection" isn't exactly a ringing endorsement, but it's the best I've got :-)
Paul
On Wed, Feb 19, 2014 at 6:53 PM, Paul Moore p.f.moore@gmail.com wrote:
So having struggled to find objections to the colon syntax, I've reached a point where I think it's the best of the alternatives. "I can't find a good enough objection" isn't exactly a ringing endorsement, but it's the best I've got :-)
Hehe. That's one way to settle it!
If anyone does have a strong objection, I do want to hear. Sometimes a thread like this starts piling off in one direction, and any voice going the other way may feel like yelling into a hurricane, but a lot of the decisions made in the PEP have been on a fairly light balance of evidence. One good shout in a different direction could change some of them. The 'as' clause is currently hanging in the balance, for instance:
Pro: Consistency with try/except statement, functionality.
Con: Introduces complexity, functionality isn't used.
It's on the knife-edge. Currently it's in, but could go out more easily than a Pommie batsman.
*dives for cover*
ChrisA
I don't know if this really amounts to a *strong* objection. To me, as much as I try to like it reading this thread, the colon just continues to feel wrong to me. Yes, of course I know the analogy with lambda, and I can even see a certain analogy with dict literals. However, far more compelling to me is making it look more like the ternary expression (which it is really basically a special case of.
In terms of keywords to put in place of the colon, the "least bad" in my mind is "return." Yes, of course, we don't actually return out of a function call or remove a call stack element (well, unless we wind up doing so in the implementation). But without fuzzing one's brain *too much* one can think of the except expression as kind of like a function call, and though of that way, 'return' makes sense.
The next "least bad" in my mind is 'else' because if preserved the parallel with 'val if cond else fallback' most closely. Sure, we need a bit more verbosity in the expression, especially when 'as' is used (and it should be included), i.e.:
x = val except SomeError as e else something(e)
But still it's basically only substituting the one 'if' for an 'except' and otherwise keeping the familiar ternary expression.
Of course, my opinion here is of very slight value, since the intuitions of our BDFL will decide the outcome, and the various alternatives are present in the PEP. And indeed, if the colon is what "wins", I'll learn to use it and be more comfortable with it.
On Wed, Feb 19, 2014 at 12:17 AM, Chris Angelico rosuav@gmail.com wrote:
On Wed, Feb 19, 2014 at 6:53 PM, Paul Moore p.f.moore@gmail.com wrote:
So having struggled to find objections to the colon syntax, I've reached a point where I think it's the best of the alternatives. "I can't find a good enough objection" isn't exactly a ringing endorsement, but it's the best I've got :-)
Hehe. That's one way to settle it!
If anyone does have a strong objection, I do want to hear. Sometimes a thread like this starts piling off in one direction, and any voice going the other way may feel like yelling into a hurricane, but a lot of the decisions made in the PEP have been on a fairly light balance of evidence. One good shout in a different direction could change some of them. The 'as' clause is currently hanging in the balance, for instance:
Pro: Consistency with try/except statement, functionality.
Con: Introduces complexity, functionality isn't used.
It's on the knife-edge. Currently it's in, but could go out more easily than a Pommie batsman.
*dives for cover*
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Wed, Feb 19, 2014 at 01:04:54AM -0800, David Mertz wrote:
I don't know if this really amounts to a *strong* objection. To me, as much as I try to like it reading this thread, the colon just continues to feel wrong to me. Yes, of course I know the analogy with lambda, and I can even see a certain analogy with dict literals. However, far more compelling to me is making it look more like the ternary expression (which it is really basically a special case of.
I don't believe that it is a special case of if-expressions. The two operators are completely unrelated (although they sometimes get used for similar purposes). LBYL and EAFP have completely different semantics. Think "hammer and nail" and "screwdriver and screw" -- they can get used for the same thing, but you use them in different ways and they work according to different principles.
I think you are being misled by the fact that both ternary operators have three arguments, and therefore there's really only a limited number of ways you can arrange them using infix notation, the most obvious being to place two symbols between the three operands.
In C, we have cond ? true-value : false-value
In Algol, we have ( cond | true-statements | false-statements )
In both of those forms, the first thing evaluated -- the condition -- is listed first. The proposed except ternary operator works similarly:
expr except Exception <whatever> fallback ^^^^ evaluate this first
where <whatever> might be spelled ":" or perhaps "return" or "import" *grin*.
But in Python, the ternary if operator has the condition listed in the middle:
true-statement if cond else false-condition ..................^^^^ evaluate this first
This truly is an odd duck. It works, at least for English speakers, but it is very different from most ternary operators, which evaluate the left-most operand first, not the middle one. So if we were to make the except operator follow the lead of if, it would look something like this:
exception except expr <whatever> default .................^^^^ evaluate this first
which is awful. So I reject your premise that we ought to make the except ternary operator look like the if ternary operator.
In terms of keywords to put in place of the colon, the "least bad" in my mind is "return." Yes, of course, we don't actually return out of a function call or remove a call stack element (well, unless we wind up doing so in the implementation). But without fuzzing one's brain *too much* one can think of the except expression as kind of like a function call, and though of that way, 'return' makes sense.
I've already talked about why I think that's an inappropriate term. So moving along:
The next "least bad" in my mind is 'else' because if preserved the parallel with 'val if cond else fallback' most closely.
So the else in a try...except statement runs when an exception does not occur, and the else in an except expression runs when an exception does occur. No offense intended, but this is how we get god-awful syntax like "for...else" :-) Great concept, lousy choice of keywords.
In a for-loop, the "else" actually isn't an else at all. Like many people, I spent years convinced that for...else executed the "else" block if the for-loop *didn't* run, as in "run this for-loop, else run this block". In reality the "else" block unconditionally runs after the for-loop. (The only way to skip the else block is to break, return or raise, which jumps right out of the for....else statement.) It's more of a "then" rather than an "else".
In this case, the except cause is not an "else" at all. We have:
expr1 except Something else expr2
=> evaluate an expression did an exception get raised? else evaluate another expression
which implies that the right-most expression should be evaluated only if *no exception occurs*. Which would be pointless.
On 19 February 2014 12:46, Steven D'Aprano steve@pearwood.info wrote:
But in Python, the ternary if operator has the condition listed in the middle:
true-statement if cond else false-condition ..................^^^^ evaluate this first
This truly is an odd duck. It works, at least for English speakers, but it is very different from most ternary operators, which evaluate the left-most operand first, not the middle one. So if we were to make the except operator follow the lead of if, it would look something like this:
exception except expr <whatever> default .................^^^^ evaluate this first
which is awful. So I reject your premise that we ought to make the except ternary operator look like the if ternary operator.
I think this is pretty much a tangent by now, but your interpretation here isn't the only way of looking at things. Consider the following alternative way of looking at it
TRUE-EXPR if COND else FALSE-EXPR
Here, TRUE-EXPR is the "expected" result, qualified by the possibility that if COND isn't true then FALSE-EXPR is the result. Looking at the if expression in that way, the parallel with the exception expression is much clearer:
EXPR except EXCEPTION return FALLBACK
Here, EXPR is the "expected" result, but if you get EXCEPTION when evaluating it then use FALLBACK instead. (Feel free to replace "return" with colon or your favourite syntax alternative).
I can't think what you mean by "most ternary operators" unless you're referring to ternary operators in languages other than Python, so I won't comment on that part of what you say.
That may not be how *you* think of the if expression, but it's how I think of it (more accurately, it's how I learned to think of it as part of understanding why Guido chose that option).
Paul
I agree with Paul that this is a tangent. Just to answer his questions:
On Wed, Feb 19, 2014 at 01:01:34PM +0000, Paul Moore wrote:
On 19 February 2014 12:46, Steven D'Aprano steve@pearwood.info wrote:
But in Python, the ternary if operator has the condition listed in the middle:
true-statement if cond else false-condition ..................^^^^ evaluate this first
This truly is an odd duck. It works, at least for English speakers, but it is very different from most ternary operators, which evaluate the left-most operand first, not the middle one. So if we were to make the except operator follow the lead of if, it would look something like this:
exception except expr <whatever> default .................^^^^ evaluate this first
which is awful. So I reject your premise that we ought to make the except ternary operator look like the if ternary operator.
I think this is pretty much a tangent by now, but your interpretation here isn't the only way of looking at things. Consider the following alternative way of looking at it
TRUE-EXPR if COND else FALSE-EXPR
Here, TRUE-EXPR is the "expected" result, qualified by the possibility that if COND isn't true then FALSE-EXPR is the result. Looking at the if expression in that way, the parallel with the exception expression is much clearer:
EXPR except EXCEPTION return FALLBACK
Here, EXPR is the "expected" result, but if you get EXCEPTION when evaluating it then use FALLBACK instead. (Feel free to replace "return" with colon or your favourite syntax alternative).
Okay, that's reasonable.
I can't think what you mean by "most ternary operators" unless you're referring to ternary operators in languages other than Python, so I won't comment on that part of what you say.
Yes. I suggested that Python's conditional operator "is very different from most ternary operators" in that the condition is in the middle rather than on the left. I don't mean that as a negative, I actually do think it works well for if/else, but it is certainly different from what other languages do.
If you look at (say) this page:
http://en.wikipedia.org/wiki/%3F:
nearly all the conditional operators apart from Python take the form:
condition SYMBOL true-expression SYMBOL false-expression
usually with ? and : as the symbols. (I'm excluding those languages that use functional notation.) Coffeescript is a exception:
SYMBOL condition SYMBOL true-expression SYMBOL false-expression
Likewise, the BETWEEN ternary operator in SQL looks like
value BETWEEN low AND high
But as you say, this is a tangent.
On Thu, Feb 20, 2014 at 12:23 AM, Steven D'Aprano steve@pearwood.info wrote:
If you look at (say) this page:
http://en.wikipedia.org/wiki/%3F:
nearly all the conditional operators apart from Python take the form:
condition SYMBOL true-expression SYMBOL false-expression
usually with ? and : as the symbols.
Most of the reason for that is because they're all deriving from each other, and if you're going to borrow syntax from someone else, you should use the same little details. Making a ternary conditional operator that uses ? and : just as C does, but switches the true-expression and false-expression, would unnecessarily confuse people. So would fiddling with its associativity, not that that stopped PHP.
C's ternary operator reads in the same order as a classic if statement:
/* C */ if (cond) true_statement; else false_statement; value = cond ? true_expression : false_expression;
# Python if cond: true_statement else: false_statement
Perl's "shove the condition to the right hand of the page" notation reads backward:
do_if_true if cond;
It first evaluates cond, and then may or may not evaluate do_if_true. Python's ternary operator similarly reads in an odd order:
true_expression if cond else false_expression
evaluates from the middle out. But it's not the only thing that evaluates backward:
def foo(x):
print("Calling foo:",x) return [0]
foo(2)[0] = foo(1)
Calling foo: 1 Calling foo: 2
The RHS is evaluated before the LHS, yet this is not a problem to us. (Maybe that's because it almost never matters?)
Is this something where people from a functional background approach the problem one way, and people from an imperative background approach it another way? Personally, I like my code to read more-or-less in the order it's written: top-to-bottom, left-to-right. Definitions before usage. But maybe that's because I grew up with a strongly imperative style.
ChrisA
On 02/19/2014 07:01 AM, Paul Moore wrote:
I think this is pretty much a tangent by now, but your interpretation here isn't the only way of looking at things. Consider the following alternative way of looking at it
TRUE-EXPR if COND else FALSE-EXPR
Here, TRUE-EXPR is the "expected" result, qualified by the possibility that if COND isn't true then FALSE-EXPR is the result. Looking at the if expression in that way, the parallel with the exception expression is much clearer:
EXPR except EXCEPTION return FALLBACK
Here, EXPR is the "expected" result, but if you get EXCEPTION when evaluating it then use FALLBACK instead. (Feel free to replace "return" with colon or your favourite syntax alternative).
-1 on return. I expect that to exit the function where ever it is in the current scope.
How about this?
def catch_else(exc, e1, e2):
... try: ... return e1() ... except exc: ... return e2() ...
catch_else(IndexError, lambda: [1, 2, 3][2], lambda: 0)
3
catch_else(IndexError, lambda: [1, 2, 3][4], lambda: 0)
0
The only bad thing about that is having to spell out lambda for each of the arguments. *1
With a short literal form for a lambda that takes no arguments.
value = catch_else(exc, \expr1, \expr2) #alternatives to ''?
The function "catch_else" could be a builtin or in functools depending on how useful it's believed to be.
The shorter lambda expression literal... or expression quote as it's referred to in other languages would be useful on it's own, and more preferable than a full lambda for things like this.
[*1] In discussion about how long it's taken for us to get away from incandescent lights, the expert brought up that there is a difference between acceptable and preferable.
Cheers, Ron
On Wed, Feb 19, 2014 at 11:17 AM, Ron Adam ron3200@gmail.com wrote:
value = catch_else(exc, \expr1, \expr2) #alternatives to ''?
λ
.. ducks.
On 02/19/2014 11:09 AM, Alexander Belopolsky wrote:
On Wed, Feb 19, 2014 at 11:17 AM, Ron Adam <ron3200@gmail.com mailto:ron3200@gmail.com> wrote:
value = catch_else(exc, \expr1, \expr2) #alternatives to '\'?
λ
.. ducks.
Hehe... So you noticed λ and \ are not that different?
The one with both legs is the one that takes arguments.
The '' is more agreeable. ;-)
On 20/02/14 02:01, Paul Moore wrote:
EXPR except EXCEPTION return FALLBACK
Here, EXPR is the "expected" result, but if you get EXCEPTION when evaluating it then use FALLBACK instead.
I don't think there's any point in forcing things to be in the same order as the if-expression if it requires mangling English grammar.
The reason we ended up with the oddly-ordered if-expression in the first place is that it *avoided* mangled English while staying within the existing set of keywords.
To introduce another construct that mangles English and/or abuses keywords just to match the odd ordering of the if-expression would be a truly foolish consistency.
I'll accept Steven's criticism of the 'else' keyword as being terrible because the semantics is opposite of a try/except/else block. But I will push back on the similarity between an except expression and a ternary expression (as Python does it with the condition in the middle).
I wasn't in love with the Python ternary with the condition in the middle when it was added. I'm still not sure I *love* it, since the C-style ternary with the condition at the front sort of feels more natural to me... well, it would if I knew how to spell it, which didn't seem to have a good answer within Python. But that is a long done deal, so we have what we have.
However, *given* the condition-in-middle form, it promotes a different understanding of the ternary expression than C programmers have. In C, we think of a condition that is pretty much equally likely to be true or false, and then list the two "branches" after that. In Python (even though it's obviously formally exactly equivalent in power), we think of the "expected" value to assign, then the condition as an "exceptional" circumstance that makes us want something else. When I use the Python ternary, it is almost always similar to:
x = normal_thing if isUnexceptional(normal_thing) else something_unusual
That really does feel a whole lot like an exception in the middle there. Well, actually, I guess it's the negation of an exception. But the point is that what come right at the start is what we *expect* to usually be assigned, then we worry about details of what to do just-in-case.
Thought of that way, it's VERY close to:
x = normal_thing except UnusualException return something_unusual
On Wed, Feb 19, 2014 at 4:46 AM, Steven D'Aprano steve@pearwood.infowrote:
On Wed, Feb 19, 2014 at 01:04:54AM -0800, David Mertz wrote:
I don't know if this really amounts to a *strong* objection. To me, as much as I try to like it reading this thread, the colon just continues to feel wrong to me. Yes, of course I know the analogy with lambda, and I
can
even see a certain analogy with dict literals. However, far more compelling to me is making it look more like the ternary expression
(which
it is really basically a special case of.
I don't believe that it is a special case of if-expressions. The two operators are completely unrelated (although they sometimes get used for similar purposes). LBYL and EAFP have completely different semantics. Think "hammer and nail" and "screwdriver and screw" -- they can get used for the same thing, but you use them in different ways and they work according to different principles.
I think you are being misled by the fact that both ternary operators have three arguments, and therefore there's really only a limited number of ways you can arrange them using infix notation, the most obvious being to place two symbols between the three operands.
In C, we have cond ? true-value : false-value
In Algol, we have ( cond | true-statements | false-statements )
In both of those forms, the first thing evaluated -- the condition -- is listed first. The proposed except ternary operator works similarly:
expr except Exception <whatever> fallback ^^^^ evaluate this first
where <whatever> might be spelled ":" or perhaps "return" or "import" *grin*.
But in Python, the ternary if operator has the condition listed in the middle:
true-statement if cond else false-condition ..................^^^^ evaluate this first
This truly is an odd duck. It works, at least for English speakers, but it is very different from most ternary operators, which evaluate the left-most operand first, not the middle one. So if we were to make the except operator follow the lead of if, it would look something like this:
exception except expr <whatever> default .................^^^^ evaluate this first
which is awful. So I reject your premise that we ought to make the except ternary operator look like the if ternary operator.
In terms of keywords to put in place of the colon, the "least bad" in my mind is "return." Yes, of course, we don't actually return out of a function call or remove a call stack element (well, unless we wind up
doing
so in the implementation). But without fuzzing one's brain *too much*
one
can think of the except expression as kind of like a function call, and though of that way, 'return' makes sense.
I've already talked about why I think that's an inappropriate term. So moving along:
The next "least bad" in my mind is 'else' because if preserved the
parallel
with 'val if cond else fallback' most closely.
So the else in a try...except statement runs when an exception does not occur, and the else in an except expression runs when an exception does occur. No offense intended, but this is how we get god-awful syntax like "for...else" :-) Great concept, lousy choice of keywords.
In a for-loop, the "else" actually isn't an else at all. Like many people, I spent years convinced that for...else executed the "else" block if the for-loop *didn't* run, as in "run this for-loop, else run this block". In reality the "else" block unconditionally runs after the for-loop. (The only way to skip the else block is to break, return or raise, which jumps right out of the for....else statement.) It's more of a "then" rather than an "else".
In this case, the except cause is not an "else" at all. We have:
expr1 except Something else expr2 => evaluate an expression did an exception get raised? else evaluate another expression
which implies that the right-most expression should be evaluated only if *no exception occurs*. Which would be pointless.
-- Steven _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 19/02/14 22:04, David Mertz wrote:
I don't know if this really amounts to a *strong* objection. To me, as much as I try to like it reading this thread, the colon just continues to feel wrong to me. Yes, of course I know the analogy with lambda, and I can even see a certain analogy with dict literals. However, far more compelling to me is making it look more like the ternary expression (which it is really basically a special case of.
The only colon-less version I've seen so far that I could live with would be
things[i] except None if IndexError
Attempts to use commas instead of keywords are visually confusing, and abuses of keywords like "except ... return" and "except ... pass" just look horrible to me.
Yes, it's inconsistent with the way things are ordered in the try statement, but I don't think that's necessarily a fatal flaw. It reads like English, so it's fairly obvious to anyone reading it what the intended meaning is, and if you find yourself writing
things[i] except IndexError if None
and you have even half your wits about you, then you'll notice that something doesn't make sense when you get to the 'if'. Also if you try to write something like
things[i] except IndexError else None
you'll find out it's wrong pretty quickly via a SyntaxError.
One other objection might be that if you want an 'as' clause it would have to be
things[i] except f(e) if IndexError as e
which puts the binding of e after its use. But that's not unprecedented -- comprehensions do this too.
On Thu, Feb 20, 2014 at 2:13 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
things[i] except IndexError if None
and you have even half your wits about you, then you'll notice that something doesn't make sense when you get to the 'if'.
Actually, you have to keep going to see if you hit an 'else', because "IndexError if None else something_else" is the same as "something_else".
ChrisA
Chris Angelico wrote:
On Thu, Feb 20, 2014 at 2:13 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
things[i] except IndexError if None
and you have even half your wits about you, then you'll notice that something doesn't make sense when you get to the 'if'.
Actually, you have to keep going to see if you hit an 'else', because "IndexError if None else something_else" is the same as "something_else".
For sanity, parentheses should probably be required for that interpretation:
things[i] except (value if cond else other_value) if IndexError
On Thu, Feb 20, 2014 at 3:23 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
Chris Angelico wrote:
On Thu, Feb 20, 2014 at 2:13 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
things[i] except IndexError if None
and you have even half your wits about you, then you'll notice that something doesn't make sense when you get to the 'if'.
Actually, you have to keep going to see if you hit an 'else', because "IndexError if None else something_else" is the same as "something_else".
For sanity, parentheses should probably be required for that interpretation:
things[i] except (value if cond else other_value) if IndexError
Unless you require them all the time, parsing's going to have to go all the way to the end of the expression before being sure of its interpretation. I'm not sure that's a good thing. Again, it may or may not be a problem for a computer lexer, but a human would have to remember to look for the else, just in case.
Of course, I don't often expect people to be often writing stuff like this, but it is technically legal:
things[i] except None if IndexError if issubclass(things, list) else KeyError
Not to mention that this case is better handled by catching LookupError - but not everyone knows about that. (The normal rule against catching more than you expect isn't likely to be a problem here. But I could imagine someone specifically avoiding LookupError in case the other sort of error gets thrown somehow.) Yes, this should be parenthesized; but operator precedence rules mean that this must have one of two meanings:
(things[i] except None if IndexError) if issubclass(things, list) else KeyError things[i] except None if (IndexError if issubclass(things, list) else KeyError)
Would you know, without looking up a precedence table, which this is? It's clear which one the programmer intended, but would you know whether the parser treats it the same way? Yet there must be an order to them.
ChrisA
On 19 February 2014 17:53, Paul Moore p.f.moore@gmail.com wrote:
On 19 February 2014 07:34, Chris Angelico rosuav@gmail.com wrote:
Have you a preferred keyword-based proposal? Does it use an existing keyword or create a new one? The floor is yours, Paul: sell me your keyword :) Bear in mind, I used to be in favour of the "expr except Exception pass default" form, so it shouldn't be too hard to push me back to there.
Honestly? No, I don't. If I need an example of non-colon syntax I choose "return" and consider "pass" because those are the 2 that seem most reasonable, and I remember "return" fastest. But I can't really argue strongly for them - whoever said it was right, *all* of the keyword approaches are picking "something that's not awful" from what we have.
My list of new keywords is basically the same as yours, but I don't think the construct is important enough to warrant a new keyword (if the if-expression couldn't justify "then", then we don't stand a chance!)
So having struggled to find objections to the colon syntax, I've reached a point where I think it's the best of the alternatives. "I can't find a good enough objection" isn't exactly a ringing endorsement, but it's the best I've got :-)
Right, unless Guido manages to pull another PEP 308 style "I have come up with a clever idea nobody else thought of, and likely only the BDFL could sell to anyone else", I think the colon based version Chris has written up in the PEP currently counts as "least bad of the alternatives proposed, and sufficiently tolerable to be judged better than the assortment of relatively ad hoc workarounds we have at the moment".
That said, I did suggest using the function return type annotation marker as a possibility, and that's not currently listed in the PEP:
value = lst[2] except IndexError -> 'No value'
I do slightly prefer that to the colon based version, although we may want to go with the generator expression approach of always requiring parentheses around it (with function call parentheses counting):
value = (lst[2] except IndexError -> 'No value')
On an unrelated tangent (as I forget which part of the thread it came up in), having to switch from the compiler checked and code completion friendly 'obj.attr' to the more opaque (from an automated code analysis point of view) 'getattr(obj, "attr", None)' is another symptom of this current limitation that should be listed in the motivation section of the PEP.
Cheers, Nick.
On Wed, Feb 19, 2014 at 11:17 PM, Nick Coghlan ncoghlan@gmail.com wrote:
Right, unless Guido manages to pull another PEP 308 style "I have come up with a clever idea nobody else thought of, and likely only the BDFL could sell to anyone else", I think the colon based version Chris has written up in the PEP currently counts as "least bad of the alternatives proposed, and sufficiently tolerable to be judged better than the assortment of relatively ad hoc workarounds we have at the moment".
That said, I did suggest using the function return type annotation marker as a possibility, and that's not currently listed in the PEP:
value = lst[2] except IndexError -> 'No value'
I missed that in the flurry. Have added it now.
On an unrelated tangent (as I forget which part of the thread it came up in), having to switch from the compiler checked and code completion friendly 'obj.attr' to the more opaque (from an automated code analysis point of view) 'getattr(obj, "attr", None)' is another symptom of this current limitation that should be listed in the motivation section of the PEP.
Good point. Also added, though I put it into Rationale rather than Motivation.
ChrisA
A bit OT: I think there should be a mention in the PEP of other languages with exception-handling expressions, like SML's
- (hd []) handle Empty => 1; val it = 1 : int
Which reminds Nick's proposal. I think the parens makes it look very clean in such simple examples.
--- Elazar
2014-02-19 14:47 GMT+02:00 Chris Angelico rosuav@gmail.com:
On Wed, Feb 19, 2014 at 11:17 PM, Nick Coghlan ncoghlan@gmail.com wrote:
Right, unless Guido manages to pull another PEP 308 style "I have come up with a clever idea nobody else thought of, and likely only the BDFL could sell to anyone else", I think the colon based version Chris has written up in the PEP currently counts as "least bad of the alternatives proposed, and sufficiently tolerable to be judged better than the assortment of relatively ad hoc workarounds we have at the moment".
That said, I did suggest using the function return type annotation marker as a possibility, and that's not currently listed in the PEP:
value = lst[2] except IndexError -> 'No value'
I missed that in the flurry. Have added it now.
On an unrelated tangent (as I forget which part of the thread it came up in), having to switch from the compiler checked and code completion friendly 'obj.attr' to the more opaque (from an automated code analysis point of view) 'getattr(obj, "attr", None)' is another symptom of this current limitation that should be listed in the motivation section of the PEP.
Good point. Also added, though I put it into Rationale rather than Motivation.
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Thu, Feb 20, 2014 at 12:00 AM, אלעזר elazarg@gmail.com wrote:
A bit OT: I think there should be a mention in the PEP of other languages with exception-handling expressions, like SML's
- (hd []) handle Empty => 1; val it = 1 : int
I'm not familiar with SML. Want to write up a paragraph that I can insert directly into the PEP?
ChrisA
From: Chris Angelico rosuav@gmail.com
Sent: Wednesday, February 19, 2014 5:31 AM
On Thu, Feb 20, 2014 at 12:00 AM, אלעזר elazarg@gmail.com wrote:
A bit OT: I think there should be a mention in the PEP of other languages with exception-handling expressions, like SML's
- (hd []) handle Empty => 1; val it = 1 : int
I'm not familiar with SML. Want to write up a paragraph that I can insert directly into the PEP?
Actually, any purely-functional languages where a function body is always an expression obviously have to do exception handling as an expression. So, a survey may be helpful here.
It's hard to map other languages to Python because most of them either allow statements inside expressions and take the value of the last statement (which must be an expression statement) as the value, or don't really have statements at all. Also, the different function-calling syntaxes can be very confusing. I'll try my best. Wikipedia has a page on exception handling syntax (http://en.wikipedia.org/wiki/Exception_handling_syntax) that gives more details of tons of languages, and I'll try to link each one to a relevant docs or tutorial page.
---
Ruby (http://www.skorks.com/2009/09/ruby-exceptions-and-exception-handling/) "begin…rescue…rescue…else…ensure…end" is an expression (potentially with statements inside it). It has the equivalent of an "as" clause, and the equivalent of bare except. And it uses no punctuation or keyword between the bare except/exception class/exception class with as clause and the value. (And yes, it's ambiguous unless you understand Ruby's statement/expression rules.)
x = begin computation() rescue MyException => e default(e) end; x = begin computation() rescue MyException default() end; x = begin computation() rescue default() end; x = begin computation() rescue MyException default() rescue OtherException other() end;
In terms of this PEP:
x = computation() except MyException as e default(e) x = computation() except MyException default(e) x = computation() except default(e) x = computation() except MyException default() except OtherException other()
---
Erlang (http://erlang.org/doc/reference_manual/expressions.html#id79284) has a try expression that looks like this:
x = try computation() catch MyException:e -> default(e) end; x = try computation() catch MyException:e -> default(e); OtherException:e -> other(e) end;
The class and "as" name are mandatory, but you can use "_" for either. There's also an optional "when" guard on each, and a "throw" clause that you can catch, which I won't get into. To handle multiple exceptions, you just separate the clauses with semicolons, which I guess would map to commas in Python. So:
x = try computation() except MyException as e -> default(e) x = try computation() except MyException as e -> default(e), OtherException as e->other_default(e)
Erlang also has a "catch" expression, which, despite using the same keyword, is completely different, and you don't want to know about it.
---
The ML family has two different ways of dealing with this, "handle" and "try"; the difference between the two is that "try" pattern-matches the exception, which gives you the effect of multiple except clauses and as clauses. In either form, the handler clause is punctuated by "=>" in some dialects, "->" in others.
To avoid confusion, I'll write the function calls in Python style.
Here's SML (http://www.cs.cmu.edu/~rwh/introsml/core/exceptions.htm)%27s "handle":
let x = computation() handle MyException => default();;
Here's OCaml (http://www2.lib.uchicago.edu/keith/ocaml-class/exceptions.html)%27s "try":
let x = try computation() with MyException explanation -> default(explanation);;
let x = try computation() with
MyException(e) -> default(e) | MyOtherException() -> other_default() | (e) -> fallback(e);;
In terms of this PEP, these would be something like:
x = computation() except MyException => default() x = try computation() except MyException e -> default() x = (try computation() except MyException as e -> default(e) except MyOtherException -> other_default() except BaseException as e -> fallback(e))
Many ML-inspired but not-directly-related languages from academia mix things up, usually using more keywords and fewer symbols. So, the Oz (http://mozart.github.io/mozart-v1/doc-1.4.0/tutorial/node5.html) would map to Python as:
x = try computation() catch MyException as e then default(e)
---
Many Lisp-derived languages, like Clojure (http://clojure.org/special_forms#Special%20Forms--(try%20expr*%20catch-claus...)), implement try/catch as special forms (if you don't know what that means, think function-like macros), so you write, effectively:
try(computation(), catch(MyException, explanation, default(explanation)))
try(computation(), catch(MyException, explanation, default(explanation)), catch(MyOtherException, explanation, other_default(explanation)))
In Common Lisp, this is done with a slightly clunkier "handler-case" macro (http://clhs.lisp.se/Body/m_hand_1.htm), but the basic idea is the same.
---
The Lisp style is, surprisingly, used by some languages that don't have macros, like Lua, where xpcall (http://www.gammon.com.au/scripts/doc.php?lua=xpcall) takes functions. Writing lambdas Python-style instead of Lua-style:
x = xpcall(lambda: expression(), lambda e: default(e))
This actually returns (true, expression()) or (false, default(e)), but I think we can ignore that part.
---
Haskell is actually similar to Lua here (except that it's all done with monads, of course):
x = do catch(lambda: expression(), lambda e: default(e))
You can write a pattern matching expression within the function to decide what to do with it; catching and re-raising exceptions you don't want is cheap enough to be idiomatic.
But Haskell infixing makes this nicer:
x = do expression() `catch` lambda: default() x = do expression() `catch` lambda e: default(e)
And that makes the parallel between the lambda colon and the except colon in the proposal much more obvious:
x = expression() except Exception: default() x = expression() except Exception as e: default(e)
---
Tcl (http://wiki.tcl.tk/902) has the other half of Lua's xpcall; catch is a function which returns true if an exception was caught, false otherwise, and you get the value out in other ways. And it's all built around the the implicit quote-and-exec that everything in Tcl is based on, making it even harder to describe in Python terms than Lisp macros, but something like:
if {[ catch("computation()") "explanation"]} { default(explanation) }
---
Smalltalk (http://smalltalk.gnu.org/wiki/exceptions) is also somewhat hard to map to Python. The basic version would be:
x := computation() on:MyException do:default()
… but that's basically Smalltalk's passing-arguments-with-colons syntax, not its exception-handling syntax.
On Thu, Feb 20, 2014 at 10:54 AM, Andrew Barnert abarnert@yahoo.com wrote:
Actually, any purely-functional languages where a function body is always an expression obviously have to do exception handling as an expression. So, a survey may be helpful here.
It's hard to map other languages to Python because most of them either allow statements inside expressions and take the value of the last statement (which must be an expression statement) as the value, or don't really have statements at all. Also, the different function-calling syntaxes can be very confusing. I'll try my best. Wikipedia has a page on exception handling syntax (http://en.wikipedia.org/wiki/Exception_handling_syntax) that gives more details of tons of languages, and I'll try to link each one to a relevant docs or tutorial page.
Thanks for that! I can incorporate that directly. As a part of the PEP, it would be explicitly placed in the public domain, and I or anyone else who edits the PEP would be allowed to edit the text you've written. Just for the record, I want to confirm that you're okay with that :)
ChrisA
On Wed, Feb 19, 2014 at 10:17:05PM +1000, Nick Coghlan wrote:
That said, I did suggest using the function return type annotation marker as a possibility, and that's not currently listed in the PEP:
value = lst[2] except IndexError -> 'No value'
I missed that too. I prefer that to nearly all of the keyword based suggestions so far. In order of most preferred to least:
+1 : +0.9 -> +0.5 then -0.5 return -1 pass, else
-> also passes the Tim Peters "grit on monitor" test.
I do slightly prefer that to the colon based version, although we may want to go with the generator expression approach of always requiring parentheses around it (with function call parentheses counting):
value = (lst[2] except IndexError -> 'No value')
I'd be happy with that solution.
On 19/02/2014 13:07, Steven D'Aprano wrote:
On Wed, Feb 19, 2014 at 10:17:05PM +1000, Nick Coghlan wrote:
That said, I did suggest using the function return type annotation marker as a possibility, and that's not currently listed in the PEP:
value = lst[2] except IndexError -> 'No value'
I missed that too. I prefer that to nearly all of the keyword based suggestions so far. In order of most preferred to least:
+1 : +0.9 -> +0.5 then -0.5 return -1 pass, else
-> also passes the Tim Peters "grit on monitor" test.
I pretty much agree. Rob Cliffe
On 02/19/2014 05:07 AM, Steven D'Aprano wrote:
On Wed, Feb 19, 2014 at 10:17:05PM +1000, Nick Coghlan wrote:
I do slightly prefer that to the colon based version, although we may want to go with the generator expression approach of always requiring parentheses around it (with function call parentheses counting):
value = (lst[2] except IndexError -> 'No value')
I'd be happy with that solution.
Same here.
-- ~Ethan~
On 19.02.2014 08:34, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 6:25 PM, Paul Moore p.f.moore@gmail.com wrote:
On 19 February 2014 00:10, Steven D'Aprano steve@pearwood.info wrote:
x = d1[key] except K d2[key] except K 0
which by the way looks horrible without the colons (sorry Paul, you have not convinced me that the Tim Peters prohibition on grit applies here).
While I did say later on yesterday that I'd become less uncomfortable with the except Whatever: form, I was never advocating just removing the colons - pick your favourite keyword-based proposal and compare with that if you want to see what I was talking about.
But that ship has sailed, for better or worse, and it looks like the except-colon form is the winner. So be it.
The except-colon form is *my* currently-preferred spelling. But when Guido took a quick look at it, he didn't like the colon form, and his preference makes a lot more difference than mine :) It remains to be seen whether the PEP convinces him one way or the other.
Have you a preferred keyword-based proposal? Does it use an existing keyword or create a new one? The floor is yours, Paul: sell me your keyword :) Bear in mind, I used to be in favour of the "expr except Exception pass default" form, so it shouldn't be too hard to push me back to there.
Overall, I think the proposed syntax is too complex and offers too many options.
The purpose of an except expression would be to solve a single common problem: that of adding default values for situations where an exception is raised.
For this purpose, all you need is a very simple form:
mylist[1] except IndexError return 0
in the same spirit as the conditional expression if - else:
http://docs.python.org/2.7/reference/expressions.html#conditional-expression...
You don't need: - support for "as" - support for multiple except clauses - introduction of a confusing colon block - new keywords
If you do need any of these, write a regular try-except block :-)
As grammar you'd get something like this:
conditional_expression ::= or_test [(if_else_expression | except_expression)] if_else_expression ::= "if" or_test "else" expression except_expression ::= "except" expression "return" expression expression ::= conditional_expression | lambda_expr
18.02.2014 17:25, Paul Moore napisał:
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
I believe that at this point (what the exception spec would be allowed to be: identifier?, tuple?, any expression?) the syntax should be identical to the statement syntax (i.e.: any expression).
Less special cases to remember.
For the same reason, I believe that tuple expressions ("except (ValueError, TypeError)") should be obligatorily enclosed with parens as long as they are obligatorily enclosed with parens in the statement syntax (i.e., probably till Python 3.13 :)).
*j
On Wed, Feb 19, 2014 at 11:19 AM, Jan Kaliszewski zuo@chopin.edu.pl wrote:
18.02.2014 17:25, Paul Moore napisał:
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
I believe that at this point (what the exception spec would be allowed to be: identifier?, tuple?, any expression?) the syntax should be identical to the statement syntax (i.e.: any expression).
Less special cases to remember.
Yes, definitely. I see little value in forcing single-exception catching.
For the same reason, I believe that tuple expressions ("except (ValueError, TypeError)") should be obligatorily enclosed with parens as long as they are obligatorily enclosed with parens in the statement syntax (i.e., probably till Python 3.13 :)).
AFAIK, the only reason to mandate the parens is to specifically disallow the Py2 syntax:
except Exception, e: pass
If that's the case, they could be optional in the expression form, as that has no Py2 equivalent.
ChrisA
19.02.2014 02:11, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 11:19 AM, Jan Kaliszewski zuo@chopin.edu.pl wrote:
18.02.2014 17:25, Paul Moore napisał:
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
I believe that at this point (what the exception spec would be allowed to be: identifier?, tuple?, any expression?) the syntax should be identical to the statement syntax (i.e.: any expression).
Less special cases to remember.
Yes, definitely. I see little value in forcing single-exception catching.
For the same reason, I believe that tuple expressions ("except (ValueError, TypeError)") should be obligatorily enclosed with parens as long as they are obligatorily enclosed with parens in the statement syntax (i.e., probably till Python 3.13 :)).
AFAIK, the only reason to mandate the parens is to specifically disallow the Py2 syntax:
except Exception, e: pass
If that's the case, they could be optional in the expression form, as that has no Py2 equivalent.
But then you must remember: in expression yes, in statement no; + additional trouble when you refactor transforming the former to the latter...
Cheers. *j
On 19/02/2014 01:40, Jan Kaliszewski wrote:
19.02.2014 02:11, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 11:19 AM, Jan Kaliszewski zuo@chopin.edu.pl wrote:
18.02.2014 17:25, Paul Moore napisał:
OTOH, there's still an argument for only allowing a single exception name in the syntax (an "identifier" rather than an "expression" in syntax terms). If you must catch multiple exceptions, give the relevant tuple a name.
I believe that at this point (what the exception spec would be allowed to be: identifier?, tuple?, any expression?) the syntax should be identical to the statement syntax (i.e.: any expression).
Less special cases to remember.
Yes, definitely. I see little value in forcing single-exception catching.
Uh yes. I thought/hoped the debate had got past this point.
For the same reason, I believe that tuple expressions ("except (ValueError, TypeError)") should be obligatorily enclosed with parens as long as they are obligatorily enclosed with parens in the statement syntax (i.e., probably till Python 3.13 :)).
I think I agree on grounds of (sorry if I'm becoming a bore, but you guessed it!) Consistency! But I don't see this as a critical issue, particularly as Python 3.13 is probably a few weeks away :-) . I think putting parens around a list of exceptions would be good style in any case. Rob Cliffe
AFAIK, the only reason to mandate the parens is to specifically disallow the Py2 syntax:
except Exception, e: pass
If that's the case, they could be optional in the expression form, as that has no Py2 equivalent.
But then you must remember: in expression yes, in statement no;
- additional trouble when you refactor transforming the former to
the latter...
Cheers. *j
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
No virus found in this message. Checked by AVG - www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6604 - Release Date: 02/18/14
On Wed, Feb 19, 2014 at 12:40 PM, Jan Kaliszewski zuo@chopin.edu.pl wrote:
AFAIK, the only reason to mandate the parens is to specifically disallow the Py2 syntax:
except Exception, e: pass
If that's the case, they could be optional in the expression form, as that has no Py2 equivalent.
But then you must remember: in expression yes, in statement no;
- additional trouble when you refactor transforming the former to
the latter...
Perhaps. But I could imagine the need for parens being weakened in a future version. Let's say 3.4/3.5 is billed as the primary target for migration from 2.7, and that after 3.7, Python 3 will fly free and be itself without worrying too much about how hard it is to port. (That quite probably won't happen, but let's pretend.) In that case, 3.8 would be allowed to relax that restriction, which would then bring the statement and expression forms in line. Alternatively, the expression form could simply have the same arbitrary requirement, just for consistency, and they could both lose it at once... or the expression form could technically not require the parens, but style guides recommend using them anyway, in case you need to change it to a statement.
I'm generally not a fan of making parens mandatory. Let the style guides argue that out.
ChrisA
On Feb 18, 2014, at 7:26, Alexander Belopolsky alexander.belopolsky@gmail.com wrote:
On Tue, Feb 18, 2014 at 10:13 AM, MRAB python@mrabarnett.plus.com wrote:
Another possibility would be to say that a bare except in an expression catches only certain "expression-oriented" exceptions, e.g. ValueError. The simplest way to do that would be to make them subclasses of an ExpressionError class.
+1
The question then becomes one of which exceptions are "expression-oriented"...
Here is my attempt to answer:
- ArithmeticError
- AttributeError
- LookupError
- TypeError ?
- ValueError ?
And Chris Angelico gave an overlapping but different list by scanning the built-in exception types.
The fact that neither of them mentioned KeyError or IndexError, which are the two paradigm cases that brought up the proposal in the first place, implies to me that any attempt to define a collection of "expression errors" is going to be as hard and as contentious as coming up with a syntax. So, if the point of it was to allow us to come up with a new syntax that only handles one exception type, I don't think it's a promising avenue.
If people do want to follow this, one thing to consider: we don't have to fiddle with the exception hierarchy; you can always override issubclass/isinstance by either using an ABC, or doing the same thing ABCMeta does. That would allow someone who wanted to catch some type of error that isn't usually considered an "expression error" but is commonly raised in expressions in their particular project to add that error.
On Wed, Feb 19, 2014 at 4:45 AM, Andrew Barnert abarnert@yahoo.com wrote:
The fact that neither of them mentioned KeyError or IndexError, which are the two paradigm cases that brought up the proposal in the first place, implies to me that any attempt to define a collection of "expression errors" is going to be as hard and as contentious as coming up with a syntax.
Both of us mentioned LookupError, which is superclass to both KeyError and IndexError, and so catches both of them.
ChrisA
On Feb 18, 2014, at 9:49, Chris Angelico rosuav@gmail.com wrote:
On Wed, Feb 19, 2014 at 4:45 AM, Andrew Barnert abarnert@yahoo.com wrote:
The fact that neither of them mentioned KeyError or IndexError, which are the two paradigm cases that brought up the proposal in the first place, implies to me that any attempt to define a collection of "expression errors" is going to be as hard and as contentious as coming up with a syntax.
Both of us mentioned LookupError, which is superclass to both KeyError and IndexError, and so catches both of them.
Sorry, I didn't see LookupError in your list. But it's clearly there. NotEnoughCoffeeError.
On Wed, Feb 19, 2014 at 5:13 AM, Andrew Barnert abarnert@yahoo.com wrote:
On Feb 18, 2014, at 9:49, Chris Angelico rosuav@gmail.com wrote:
On Wed, Feb 19, 2014 at 4:45 AM, Andrew Barnert abarnert@yahoo.com wrote:
The fact that neither of them mentioned KeyError or IndexError, which are the two paradigm cases that brought up the proposal in the first place, implies to me that any attempt to define a collection of "expression errors" is going to be as hard and as contentious as coming up with a syntax.
Both of us mentioned LookupError, which is superclass to both KeyError and IndexError, and so catches both of them.
Sorry, I didn't see LookupError in your list. But it's clearly there. NotEnoughCoffeeError.
Though you do (effectively) raise another objection to that proposal, namely that programmers will never be quite sure what's caught and what's not. It'll become magic. Can you, without looking anything up, name all the exception types that would be caught by (LookupError, UnicodeError, OSError) ? And that's only part of my list.
ChrisA
On Wed, Feb 19, 2014 at 4:45 AM, Andrew Barnert abarnert@yahoo.com wrote:
If people do want to follow this, one thing to consider: we don't have to fiddle with the exception hierarchy; you can always override issubclass/isinstance by either using an ABC, or doing the same thing ABCMeta does. That would allow someone who wanted to catch some type of error that isn't usually considered an "expression error" but is commonly raised in expressions in their particular project to add that error.
Actually, I'm not sure that you can. Give it a try! The only thing you can try to catch is a subclass of BaseException. But creating a tuple would work.
ChrisA
On Feb 18, 2014, at 9:51, Chris Angelico rosuav@gmail.com wrote:
On Wed, Feb 19, 2014 at 4:45 AM, Andrew Barnert abarnert@yahoo.com wrote:
If people do want to follow this, one thing to consider: we don't have to fiddle with the exception hierarchy; you can always override issubclass/isinstance by either using an ABC, or doing the same thing ABCMeta does. That would allow someone who wanted to catch some type of error that isn't usually considered an "expression error" but is commonly raised in expressions in their particular project to add that error.
Actually, I'm not sure that you can. Give it a try! The only thing you can try to catch is a subclass of BaseException. But creating a tuple would work.
But if I define two new BaseException subclasses, and register one as a subclass of the other after the fact, I believe it works. (I can't test until I get in front of a computer with Python 3 on it.) That's what I was suggesting--being able to add thirdpartymodule.error to the expression errors, not being able to add, say, deque.
Alexander Belopolsky wrote:
- ArithmeticError
- AttributeError
- LookupError
- TypeError ?
- ValueError ?
I'd also include EnvironmentError.
I wouldn't include TypeError -- that's much more likely to indicate a bug.
But this list is getting rather long, which makes me think it's not such a good idea to have a default exception list after all.
(BTW, why doesn't ArithmeticError inherit from ValueError?)
18.02.2014 16:13, MRAB wrote:
Another possibility would be to say that a bare except in an expression catches only certain "expression-oriented" exceptions, e.g. ValueError. The simplest way to do that would be to make them subclasses of an ExpressionError class. The question then becomes one of which exceptions are "expression-oriented"...
I believe it is not a good idea -- as:
* Explicit is better than implicit™.
* When dealing with exceptions, catching too much is in fact much worse than catching too little (at least most often).
Cheers. *j