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. 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').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
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
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
javascript: wrote:
On Feb 12, 2014, at 9:02 PM, Ram Rachum
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
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').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. -- \ “The whole area of [treating source code as intellectual | `\ property] is almost assuring a customer that you are not going | _o__) to do any innovation in the future.” —Gary Barnett | Ben Finney
Ben Finney
Ram Rachum
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').text except HttpError: "Can't access data" for being explicit and clarifying what to expect. -- \ “I wish there was a knob on the TV to turn up the intelligence. | `\ There's a knob called ‘brightness’ but it doesn't work.” | _o__) —Eugene P. Gallagher | Ben Finney
From: Ben Finney
Subject: Re: [Python-ideas] except expression
Ben Finney
writes: Ram Rachum
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.
Why not use yield instead of else?
foo = something() except BazException yield "bar"
On Feb 12, 2014 5:56 PM, "Andrew Barnert"
From: Ben Finney
Sent: Wednesday, February 12, 2014 4:28 PM
Subject: Re: [Python-ideas] except expression
Ben Finney
writes: Ram Rachum
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
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"
On Thu, Feb 13, 2014 at 2:08 PM, Amber Yust
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"
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"
wrote: On Thu, Feb 13, 2014 at 2:08 PM, Amber Yust
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
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"
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"
wrote: On Thu, Feb 13, 2014 at 2:08 PM, Amber Yust
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
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
: 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"
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"
wrote: On Thu, Feb 13, 2014 at 2:08 PM, Amber Yust
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"
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"
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"
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 -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Feb 13 2014)
Python Projects, Consulting and Support ... http://www.egenix.com/ mxODBC.Zope/Plone.Database.Adapter ... http://zope.egenix.com/ mxODBC, mxDateTime, mxTextTools ... http://python.egenix.com/
2014-02-12: Released mxODBC.Connect 2.0.4 ... http://egenix.com/go53 ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/
On 13 February 2014 20:10, M.-A. Lemburg
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
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
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
of relatively ad hoc alternative solutions (in particular, the
On 13 February 2014 20:10, M.-A. Lemburg
wrote: proliferation 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.
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
: On 13 February 2014 20:10, M.-A. Lemburg
wrote: General comment: like Raymond, I'm inclined to favour a nice expression friendly exception handling syntax, precisely because of the
of relatively ad hoc alternative solutions (in particular, the
On 13.02.2014 10:24, Nick Coghlan wrote: proliferation 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.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
On 13 February 2014 20:10, M.-A. Lemburg
wrote: 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
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
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
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:
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
On 15 February 2014 07:29, Jan Kaliszewski
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. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Sat, Feb 15, 2014 at 11:46 AM, Nick Coghlan
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. -- Terry Jan Reedy
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
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() -- \ “Intellectual property is to the 21st century what the slave | `\ trade was to the 16th.” —David Mertz | _o__) | Ben Finney
On 02/13/2014 12:10 PM, Ben Finney wrote:
spir
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
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. -- Steven
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. -- Terry Jan Reedy
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 Jan Reedy
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. -- Greg
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. -- Greg
On Thu, Feb 13, 2014 at 2:20 PM, Amber Yust
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
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
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
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
On Thu, Feb 13, 2014 at 10:36 PM, Paul Moore
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
On Thu, Feb 13, 2014 at 1:50 PM, Chris Angelico
wrote: On Thu, Feb 13, 2014 at 10:36 PM, Paul Moore
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
wrote: On Thu, Feb 13, 2014 at 1:50 PM, Chris Angelico
wrote: On Thu, Feb 13, 2014 at 10:36 PM, Paul Moore
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:
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,
On 13 February 2014 11:25, spir
wrote: 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
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
[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
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
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.
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
On Thu, Feb 13, 2014 at 3:25 AM, spir
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
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
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
On Thu, Feb 13, 2014 at 1:47 PM, spir
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
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
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
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
On Thu Feb 13 2014 at 2:00:00 PM, Zachary Ware
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" -- Zach PS: I know Gmail makes it harder than it needs to be, but could you please try to avoid top-posting? (This topic doesn't need to be derailed into another debate about top-posting though, so please either follow this suggestion or ignore it entirely :))
On Fri, Feb 14, 2014 at 9:11 AM, Zachary Ware
On Thu, Feb 13, 2014 at 4:02 PM, Amber Yust
wrote: <cut> On Thu Feb 13 2014 at 2:00:00 PM, Zachary Ware
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. -- Greg
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
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
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."
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
: 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. -- Steven
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
On Sun, Feb 16, 2014 at 11:16 PM, Philipp A.
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
(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. -- Steven
On Mon, Feb 17, 2014 at 4:37 AM, Steven D'Aprano
(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. -- Greg
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. -- Greg
On Sun, Feb 16, 2014 at 10:17 AM, Greg Ewing
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
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
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
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. -- Steven
On Sun, Feb 16, 2014 at 2:35 PM, Steven D'Aprano
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
On 16 February 2014 14:04, Chris Angelico
PEP: XXX Title: Exception-catching expressions Version: $Revision$ Last-Modified: $Date$ Author: Chris Angelico
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. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Mon, Feb 17, 2014 at 1:37 AM, Nick Coghlan
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 "
", 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
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. -- Steven
On Mon, Feb 17, 2014 at 4:29 PM, Steven D'Aprano
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. -- Greg
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. -- Greg
On 18 Feb 2014 08:57, "Greg Ewing"
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
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
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. -- Greg
On Tue, Feb 18, 2014 at 9:45 AM, Greg Ewing
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. -- Greg
On Tue, Feb 18, 2014 at 5:23 PM, Greg Ewing
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
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
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. -- Greg
On Tue, Feb 18, 2014 at 6:12 PM, Greg Ewing
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
On Tue, Feb 18, 2014 at 6:12 PM, Greg Ewing
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
wrote: On Tue, Feb 18, 2014 at 6:12 PM, Greg Ewing
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
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
On 18 February 2014 12:49, MRAB
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
On Wed, Feb 19, 2014 at 12:00 AM, Paul Moore
wrote: On 18 February 2014 12:49, MRAB
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
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
On Tue, Feb 18, 2014 at 9:07 AM, Paul Moore
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
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
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
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
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? -- Steven
On Wed, Feb 19, 2014 at 10:34 AM, Steven D'Aprano
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
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
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
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
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
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
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 2014-02-18 15:51, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 2:41 AM, Alexander Belopolsky
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
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.) -- Steven
On 18 February 2014 15:51, Chris Angelico
On Wed, Feb 19, 2014 at 2:41 AM, Alexander Belopolsky
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
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
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
On 18 February 2014 16:43, Chris Angelico
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:
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
On 18 February 2014 16:43, Chris Angelico
wrote: 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
On 18/02/2014 16:56, Paul Moore 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
On 18 February 2014 16:43, Chris Angelico
wrote: 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/
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
On Wed, Feb 19, 2014 at 3:25 AM, Paul Moore
wrote: 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
On 18/02/2014 16:43, Chris Angelico wrote: 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
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
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
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
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
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
On 18 February 2014 15:51, Chris Angelico
wrote: On Wed, Feb 19, 2014 at 2:41 AM, Alexander Belopolsky
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
mailto:p.f.moore@gmail.com> wrote: On 18 February 2014 15:51, Chris Angelico
mailto:rosuav@gmail.com> wrote: > On Wed, Feb 19, 2014 at 2:41 AM, Alexander Belopolsky > 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. -- Greg
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?) -- Steven
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
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 Wed, Feb 19, 2014 at 11:10 AM, Steven D'Aprano
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
On 19/02/2014 01:00, Chris Angelico wrote: 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
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
On Tue, Feb 18, 2014 at 7:10 PM, Steven D'Aprano
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
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
On 19 February 2014 00:10, Steven D'Aprano
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
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
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
On Wed, Feb 19, 2014 at 6:53 PM, Paul Moore
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/
-- 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.
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
On 19 February 2014 12:46, Steven D'Aprano
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
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. -- Steven
On Thu, Feb 20, 2014 at 12:23 AM, Steven D'Aprano
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 02/19/2014 11:09 AM, Alexander Belopolsky wrote:
On Wed, Feb 19, 2014 at 11:17 AM, Ron Adam
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. -- Greg
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
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/
-- 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.
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. -- Greg
On Thu, Feb 20, 2014 at 2:13 PM, Greg Ewing
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
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 -- Greg
On Thu, Feb 20, 2014 at 3:23 PM, Greg Ewing
Chris Angelico wrote:
On Thu, Feb 20, 2014 at 2:13 PM, Greg Ewing
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
On 19 February 2014 07:34, Chris Angelico
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. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Wed, Feb 19, 2014 at 11:17 PM, Nick Coghlan
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
On Wed, Feb 19, 2014 at 11:17 PM, Nick Coghlan
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, אלעזר
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
On Thu, Feb 20, 2014 at 12:00 AM, אלעזר
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)'s "handle": let x = computation() handle MyException => default();; Here's OCaml (http://www2.lib.uchicago.edu/keith/ocaml-class/exceptions.html)'s "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
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. -- Steven
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
wrote: On 19 February 2014 00:10, Steven D'Aprano
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 -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Feb 19 2014)
Python Projects, Consulting and Support ... http://www.egenix.com/ mxODBC.Zope/Plone.Database.Adapter ... http://zope.egenix.com/ mxODBC, mxDateTime, mxTextTools ... http://python.egenix.com/
2014-02-12: Released mxODBC.Connect 2.0.4 ... http://egenix.com/go53 ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/
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
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
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
19.02.2014 02:11, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 11:19 AM, Jan Kaliszewski
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,
On 19/02/2014 01:40, Jan Kaliszewski wrote: 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
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
On Tue, Feb 18, 2014 at 10:13 AM, 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.
+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
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
On Wed, Feb 19, 2014 at 4:45 AM, Andrew Barnert
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
On Feb 18, 2014, at 9:49, Chris Angelico
wrote: On Wed, Feb 19, 2014 at 4:45 AM, Andrew Barnert
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
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
On Wed, Feb 19, 2014 at 4:45 AM, Andrew Barnert
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?) -- Greg
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
On Wed, Feb 19, 2014 at 1:07 AM, Paul Moore
On 18 February 2014 13:37, Chris Angelico
wrote: On Wed, Feb 19, 2014 at 12:00 AM, Paul Moore
wrote: On 18 February 2014 12:49, MRAB
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 :-)
Whether it's a colon or another keyword, _something_ is necessary in there, if it's permissible to specify an exception type: sum(x[3] except IndexError 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples) How would you parse each of those, without a separator? One option might be to have a separator that's present only when the exception type is listed. For instance: sum(x[3] except(IndexError) 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples) Does that sort of thing have enough support to be added to the PEP? I'm not sure it gives us anything over the colon notation, which has the benefit of being consistent with the statement form (and is stylistically similar to lambda, so it's not grossly inappropriate to an expression context). The separator can only be omitted if there's exactly two parts to this new syntax, the "try-me" expression and the "give this instead if any exception is thrown" one. And that's the form that I'm strongly disliking, as I said in the next bit.
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.
Fair point. This should be kept simple. So, let's see. I don't feel like crafting a regular expression that searches the standard library for any one-line try block that assigns to something, so I guess I should figure out how to use ast features other than literal_eval... and it's not making it easy on me. But we'll get there. ChrisA
On 18 February 2014 15:05, Chris Angelico
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 :-)
Whether it's a colon or another keyword, _something_ is necessary in there, if it's permissible to specify an exception type:
sum(x[3] except IndexError 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
How would you parse each of those, without a separator?
With "return" as a separator. I said that above. Was I not clear?
One option might be to have a separator that's present only when the exception type is listed. For instance:
sum(x[3] except(IndexError) 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
Does that sort of thing have enough support to be added to the PEP? I'm not sure it gives us anything over the colon notation, which has the benefit of being consistent with the statement form (and is stylistically similar to lambda, so it's not grossly inappropriate to an expression context)
I remain less than happy with the colon notation, although I will concede that the basic x[3] except IndexError: 0 form is not horrible - it just becomes horrible very, very fast when people try to do anything more complicated than that. On the other hand, none of the examples of *any* of the proposed syntaxes look compelling to me - they are either too simple to be realistic, or so complex that they should be factored out into multiple statements, in my view. Paul
On Wed, Feb 19, 2014 at 2:56 AM, Paul Moore
On 18 February 2014 15:05, Chris Angelico
wrote: 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 :-)
Whether it's a colon or another keyword, _something_ is necessary in there, if it's permissible to specify an exception type:
sum(x[3] except IndexError 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
How would you parse each of those, without a separator?
With "return" as a separator. I said that above. Was I not clear?
Sure. Sorry, got a bit of brain-fry at the moment; misinterpreted your last sentence three chevrons in. So yeah, that would be: sum(x[3] except IndexError return 0 for x in list_of_tuples) sum(x[3] except return 0 for x in list_of_tuples)
I remain less than happy with the colon notation, although I will concede that the basic
x[3] except IndexError: 0
form is not horrible - it just becomes horrible very, very fast when people try to do anything more complicated than that. On the other hand, none of the examples of *any* of the proposed syntaxes look compelling to me - they are either too simple to be realistic, or so complex that they should be factored out into multiple statements, in my view.
The same can be said for if/else and the short-circuiting uses of and/or, both of which have wide-spread support, and not just from folks like me who grew up with C. I'm currently working on finding a bunch of examples from the stdlib that could be translated. Will post them once I get the script sorted out. As it's currently 3AM, though, that means I need to fry up some bacon or something before I continue :) ChrisA
On Wed, Feb 19, 2014 at 3:06 AM, Chris Angelico
I'm currently working on finding a bunch of examples from the stdlib that could be translated. Will post them once I get the script sorted out. As it's currently 3AM, though, that means I need to fry up some bacon or something before I continue :)
Alright, results are in. Script: https://github.com/Rosuav/ExceptExpr/blob/master/find_except_expr.py Output: https://github.com/Rosuav/ExceptExpr/blob/master/candidates.txt Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py The last one is the most readable. I've collected up a bunch of viable candidates for translation. (Note that I'm not advocating going through and editing these files for no other reason. That'd just be code churn. But these are cases where, had this feature existed when that code was written, it could have been taken advantage of.) My script is currently _very_ simplistic. It looks *only* for assignments, so it won't find something like this: try: foo.append(args[0]) except IndexError: foo.append('') which is correct, because it's impossible to know whether foo.append() will raise IndexError. (Chances are it won't, especially if we know foo is a list, for instance.) It's not safe to directly translate that sort of thing. It might, however, be something worth improving, as it narrows the scope of the except clause. But if that same code is written thus: try: tmp = args[0] except IndexError: tmp = '' foo.append(tmp) then the script _will_ find it, and then it really is a candidate for editing. Point to note: Apart from one instance, where it wasn't being used anyway, I found not a single instance of 'as' being used. There was one case where sys.exc_info() was referenced, though, so this may just mean that the stdlib files in question are maintaining compatibility with old versions of Python. I didn't look at most of the tests. The script found 195 plausible try/except blocks, of which 37 have the word "test" in the name;that leaves 158 that are likely to benefit from this change. There are a couple of false positives, but not many. Next is to figure out what else is a candidate for editing. Here's my current criteria, straight from the docstring: """Report on 'simple' try/except statements. The definition of simple is: 1. No else or finally clause 2. Only one except clause (currently) 3. Exactly one statement in the try clause 4. Exactly one statement in each except clause 5. Each of those statements is the same type. 6. That type is one that could be an expression. 7. Those statements are all compatible. The last two are the trickiest. Currently I'm looking only for assignments, where both try and except assign to the same target. This is, however, too narrow.""" Interestingly, removing criterion 2 gives us three additional examples out of the test suite, and nothing else. There are no cases outside of the test suite that look like this: try: x = some_calculation except ValueError: x = something_else except OverflowError: x = different_thing (The test suite has one case with those exact two exceptions, and a pair that use OverflowError and ZeroDivisionError. In each case, they're comparing two actions to make sure they give the same result, where "throwing OverflowError" is a result like any other.) Conclusions: The need for chained exception catching might not be so great after all, and even the 'as' keyword isn't as (heh heh) important as I thought it was. Alternate conclusion: My sample is too small. Need more. Data, you have the helm. ChrisA
On 18 February 2014 20:55, Chris Angelico
Alright, results are in.
Script: https://github.com/Rosuav/ExceptExpr/blob/master/find_except_expr.py Output: https://github.com/Rosuav/ExceptExpr/blob/master/candidates.txt Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py
Great work! Looking at the annotated examples, my conclusions would be: 1. The "expr except Exception: default" syntax is actually a lot more readable in context than I expected. 2. But sometimes in more complex cases the similarity with the statement form hurts readability. 3. The win is noticeable on the one-line assignments 4. You had a few cases where you could have (should have?) translated to 3-arg getattr or similar. I'm not quite sure what that says ("if you have a hammer everything looks like a nail"?) 5. The longer examples typically look more confusing to me than the originals. 6. As you noted, nothing much used multiple exceptions or as. I think this implies they are of marginal benefit at best (but I'm guessing just as much as you). Based on this sample, my main worry with the new syntax is that people will over-use it. Up to line 125 of the examples I'd say about 50% seem like wins. After that point the only ones I like are Lib/tarfile.py and Tools/unicode/comparecodecs.py. Overall, I think this is a reasonable case for the PEP, but it's not overwhelming. Which I guess about mirrors my gut feeling for how useful the new construct would be. Paul
On Wed, Feb 19, 2014 at 8:20 AM, Paul Moore
Great work!
Looking at the annotated examples, my conclusions would be:
1. The "expr except Exception: default" syntax is actually a lot more readable in context than I expected. 2. But sometimes in more complex cases the similarity with the statement form hurts readability.
Interesting. I'm not sure that the similarity is itself hurting readability. I would agree, though, that several of the examples I gave should *not* be translated, that it's a judgment call. Especially where I said "Could become" rather than "Becomes", I'm dubious about the value of translation.
3. The win is noticeable on the one-line assignments
Absolutely. Converting eight lines with zig-zag indentation into two lines, still not exceeding 80 chars, is a huge win. I'm really liking this one: g = grp.getgrnam(tarinfo.gname)[2] except KeyError: tarinfo.gid u = pwd.getpwnam(tarinfo.uname)[2] except KeyError: tarinfo.uid I've had code exactly like this, where there are 4-8 parallel differences all down the line (in this case, that's g/u, grp/pwd, getgrnam/getpwnam, gname/uname, gid/uid - fiveof them). Having the two lines perfectly aligned up and down makes it really easy to see (a) the commonality, and (b) the differences. In the original, would you notice if one of the lines had the wrong variable name, for instance - if groups were being looked up with tarinfo.uname? You might not even notice for a long time, if your group and user names are the same. But laid out like this, it's much easier to see.
4. You had a few cases where you could have (should have?) translated to 3-arg getattr or similar. I'm not quite sure what that says ("if you have a hammer everything looks like a nail"?)
In examples.py? The code there is generally meant to be a direct translation of the try/except. Maybe there's an even better way to do some of it, but that's not the point of those snippets. (Though, if there's any that really definitely should be done differently, I'll just drop them, as they're not useful examples.)
5. The longer examples typically look more confusing to me than the originals.
Yeah, which is why I shoved those down to the bottom. They're not really there to support the proposal, they're just showing what it would look like if you did do it that way. Some of them might be deemed better, others will be deemed worse; definitely they don't justify adding syntax to the language. The value of this is the shorter ones.
6. As you noted, nothing much used multiple exceptions or as. I think this implies they are of marginal benefit at best (but I'm guessing just as much as you).
This could be a hugely biased sample. Does anyone want to grab that script and run it across a Py3-only codebase? I suspect quite a lot of the Python stdlib is kept Py2-compatible, if only because stuff hasn't needed to be edited since Py2 compat was important. That would explain the use of sys.exc_info rather than as.
Based on this sample, my main worry with the new syntax is that people will over-use it. Up to line 125 of the examples I'd say about 50% seem like wins. After that point the only ones I like are Lib/tarfile.py and Tools/unicode/comparecodecs.py.
Hmm. I'd have thought the proportion higher than that. Not worth doing would be the ones where it's too long to fit onto a single line, so the expression-except would need to be wrapped; pdb.py:461, pdb.py:644, and email/_header_value_parser.py:1644 would be like that. All the rest, though, I don't see a problem with - up to the line 125 boundary, that is. Agreed that after that it's a lot more spotty.
Overall, I think this is a reasonable case for the PEP, but it's not overwhelming. Which I guess about mirrors my gut feeling for how useful the new construct would be.
I expect it'll be no more useful than ternary if... but also no less useful than ternary if. It'll have its place in the Python programmer's toolbox, without warping every single piece of code. ChrisA
On 19 Feb 2014 07:20, "Paul Moore"
On 18 February 2014 20:55, Chris Angelico
wrote: Alright, results are in.
Script: https://github.com/Rosuav/ExceptExpr/blob/master/find_except_expr.py Output: https://github.com/Rosuav/ExceptExpr/blob/master/candidates.txt Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py
Great work!
Indeed! On the syntax front, one option to keep in mind is the "yield expression" solution: require the surrounding parentheses, except when it is used as a standalone statement, as the sole expression on the RHS of an assignment statement, or (perhaps) as the sole positional argument to a function call. That approach keeps the simple cases clean, while making more complex cases a bit easier to read.
Looking at the annotated examples, my conclusions would be:
1. The "expr except Exception: default" syntax is actually a lot more readable in context than I expected. 2. But sometimes in more complex cases the similarity with the statement form hurts readability. 3. The win is noticeable on the one-line assignments 4. You had a few cases where you could have (should have?) translated to 3-arg getattr or similar. I'm not quite sure what that says ("if you have a hammer everything looks like a nail"?) 5. The longer examples typically look more confusing to me than the
originals.
6. As you noted, nothing much used multiple exceptions or as. I think this implies they are of marginal benefit at best (but I'm guessing just as much as you).
I think it's worth keeping the name binding (since that would be very hard to add later), but restricting the initial proposal to a single except clause would make sense. Without the block structure to help, it is awfully difficult to associate multiple except clauses with their corresponding initial expression, and if you really want to have different results for different exceptions, you can pass the caught exception to a translator function.
Based on this sample, my main worry with the new syntax is that people will over-use it. Up to line 125 of the examples I'd say about 50% seem like wins. After that point the only ones I like are Lib/tarfile.py and Tools/unicode/comparecodecs.py.
This was one of the main concerns with PEP 308 as well, and the general principle of "use it only if it helps readability" seems to have kept that under control.
Overall, I think this is a reasonable case for the PEP, but it's not overwhelming. Which I guess about mirrors my gut feeling for how useful the new construct would be.
As with PEP 308, I think this is more about the existing workarounds for the lack of a language level solution causing enough problems that the new syntax is being suggested primarily to say "stop doing that!". Cheers, Nick.
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/
From: Chris Angelico
Alright, results are in.
Script: https://github.com/Rosuav/ExceptExpr/blob/master/find_except_expr.py Output: https://github.com/Rosuav/ExceptExpr/blob/master/candidates.txt Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py
The last one is the most readable. I've collected up a bunch of viable candidates for translation. (Note that I'm not advocating going through and editing these files for no other reason. That'd just be code churn. But these are cases where, had this feature existed when that code was written, it could have been taken advantage of.)
It's interesting that some of these call functions that have a fallback-value parameter, so it may be worth asking why they weren't already written as expressions, and whether the new exception expression would be better. Taking one example at random: # Lib/pdb.py:433: try: func = getattr(self, 'do_' + cmd) except AttributeError: func = self.default func = getattr(self, 'do_' + cmd, self.default) func = getattr(self, 'do_' + cmd) except AttributeError: self.default These aren't identical: using the existing default parameter to getattr would require accessing self.default. Is that guaranteed to exist? (And to be relatively cheap to access?) If so, then presumably there's some reason for the more explicit form, and I don't think the except-expression version would be acceptable if the three-argument version isn't. But if not, I think that's a clear win for this proposal: Here's something we would have liked to write as an expression, but, even with a function that already has a default parameter, we couldn't do so without the except-expression. There are even more cases like this: # Lib/sysconfig.py:529: try: _CONFIG_VARS['abiflags'] = sys.abiflags except AttributeError: # sys.abiflags may not be defined on all platforms. _CONFIG_VARS['abiflags'] = '' … while you _could_ replace this with getattr(sys, 'abiflags', ''), only a madman would do so… but in a language with ".?"-type syntax you'd almost certainly use that, and with this proposal, sys.abiflags except AttributeError: '' is equivalent and much more readable. So, that looks like another win.
My script is currently _very_ simplistic. It looks *only* for
assignments, so it won't find something like this:
try: foo.append(args[0]) except IndexError: foo.append('')
which is correct, because it's impossible to know whether foo.append() will raise IndexError. (Chances are it won't, especially if we know foo is a list, for instance.) It's not safe to directly translate that sort of thing. It might, however, be something worth improving, as it narrows the scope of the except clause. But if that same code is written thus:
try: tmp = args[0] except IndexError: tmp = '' foo.append(tmp)
then the script _will_ find it, and then it really is a candidate for editing.
Point to note: Apart from one instance, where it wasn't being used anyway, I found not a single instance of 'as' being used. There was one case where sys.exc_info() was referenced, though, so this may just mean that the stdlib files in question are maintaining compatibility with old versions of Python.
I didn't look at most of the tests. The script found 195 plausible try/except blocks, of which 37 have the word "test" in the name;that leaves 158 that are likely to benefit from this change. There are a couple of false positives, but not many.
Next is to figure out what else is a candidate for editing. Here's my current criteria, straight from the docstring:
"""Report on 'simple' try/except statements.
The definition of simple is: 1. No else or finally clause 2. Only one except clause (currently) 3. Exactly one statement in the try clause 4. Exactly one statement in each except clause 5. Each of those statements is the same type. 6. That type is one that could be an expression. 7. Those statements are all compatible.
The last two are the trickiest. Currently I'm looking only for assignments, where both try and except assign to the same target. This is, however, too narrow."""
Interestingly, removing criterion 2 gives us three additional examples out of the test suite, and nothing else. There are no cases outside of the test suite that look like this:
try: x = some_calculation except ValueError: x = something_else except OverflowError: x = different_thing
(The test suite has one case with those exact two exceptions, and a pair that use OverflowError and ZeroDivisionError. In each case, they're comparing two actions to make sure they give the same result, where "throwing OverflowError" is a result like any other.)
Conclusions: The need for chained exception catching might not be so great after all, and even the 'as' keyword isn't as (heh heh) important as I thought it was.
Alternate conclusion: My sample is too small. Need more. Data, you have the helm.
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 19 Feb 2014 06:56, "Chris Angelico"
My script is currently _very_ simplistic. It looks *only* for assignments, so it won't find something like this:
try: foo.append(args[0]) except IndexError: foo.append('')
which is correct, because it's impossible to know whether foo.append() will raise IndexError. (Chances are it won't, especially if we know foo is a list, for instance.) It's not safe to directly translate that sort of thing. It might, however, be something worth improving, as it narrows the scope of the except clause. But if that same code is written thus:
try: tmp = args[0] except IndexError: tmp = '' foo.append(tmp)
then the script _will_ find it, and then it really is a candidate for
editing. This example should go in the PEP - the except clause in the example code is almost certainly too broad, but the fix is rather ugly. With the PEP, the except clause can easily be moved inside the expression so it doesn't cover the append() call: foo.append((args[0] except IndexError: "")) Cheers, Nick.
On 18/02/2014 22:56, Nick Coghlan wrote:
On 19 Feb 2014 06:56, "Chris Angelico"
mailto:rosuav@gmail.com> wrote: My script is currently _very_ simplistic. It looks *only* for assignments, so it won't find something like this:
try: foo.append(args[0]) except IndexError: foo.append('')
which is correct, because it's impossible to know whether foo.append() will raise IndexError. (Chances are it won't, especially if we know foo is a list, for instance.) It's not safe to directly translate that sort of thing. It might, however, be something worth improving, as it narrows the scope of the except clause. But if that same code is written thus:
try: tmp = args[0] except IndexError: tmp = '' foo.append(tmp)
then the script _will_ find it, and then it really is a candidate
for editing.
This example should go in the PEP - the except clause in the example code is almost certainly too broad, but the fix is rather ugly.
With the PEP, the except clause can easily be moved inside the expression so it doesn't cover the append() call:
foo.append((args[0] except IndexError: ""))
Cheers, Nick.
Beautiful catch, Nick! Shows that this proposal not only gives a neater way of writing code without changing the semantics, but may actually point the way to _improving_ existing (or new) code. 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/6604 - Release Date: 02/18/14
On 02/18/2014 12:55 PM, Chris Angelico wrote:
Conclusions: The need for chained exception catching might not be so great after all, and even the 'as' keyword isn't as (heh heh) important as I thought it was.
I wouldn't base that conclusion on the lack of seeing the new syntax in the stdlib. The stdlib works -- going through and changing working code to use the new syntax is at best code churn, and at worst (and more likely) introducing new bugs. -- ~Ethan~
On 2014-02-18 20:55, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 3:06 AM, Chris Angelico
wrote: I'm currently working on finding a bunch of examples from the stdlib that could be translated. Will post them once I get the script sorted out. As it's currently 3AM, though, that means I need to fry up some bacon or something before I continue :)
Alright, results are in.
Script: https://github.com/Rosuav/ExceptExpr/blob/master/find_except_expr.py Output: https://github.com/Rosuav/ExceptExpr/blob/master/candidates.txt Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py
The last one is the most readable. I've collected up a bunch of viable candidates for translation. (Note that I'm not advocating going through and editing these files for no other reason. That'd just be code churn. But these are cases where, had this feature existed when that code was written, it could have been taken advantage of.)
At least one of the examples doesn't need this proposed addition. "getattr" can accept a default, so: func = getattr(self, 'do_' + cmd) except AttributeError: self.default can be written as: func = getattr(self, 'do_' + cmd, self.default) [snip]
On 02/18/2014 03:58 PM, MRAB wrote:
On 2014-02-18 20:55, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 3:06 AM, Chris Angelico
wrote: I'm currently working on finding a bunch of examples from the stdlib that could be translated. Will post them once I get the script sorted out. As it's currently 3AM, though, that means I need to fry up some bacon or something before I continue :)
Alright, results are in.
Script: https://github.com/Rosuav/ExceptExpr/blob/master/find_except_expr.py Output: https://github.com/Rosuav/ExceptExpr/blob/master/candidates.txt Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py
The last one is the most readable. I've collected up a bunch of viable candidates for translation. (Note that I'm not advocating going through and editing these files for no other reason. That'd just be code churn. But these are cases where, had this feature existed when that code was written, it could have been taken advantage of.)
At least one of the examples doesn't need this proposed addition.
"getattr" can accept a default, so:
func = getattr(self, 'do_' + cmd) except AttributeError: self.default
can be written as:
func = getattr(self, 'do_' + cmd, self.default)
That depends on whether it's okay to evaluate self.default when it's not needed. -- ~Ethan~
18.02.2014 21:55, Chris Angelico wrote:
Script: https://github.com/Rosuav/ExceptExpr/blob/master/find_except_expr.py Output: https://github.com/Rosuav/ExceptExpr/blob/master/candidates.txt Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py
Excellent! :) One more minor motivating case: q = queue.Queue(maxsize=10) ... item = q.get_nowait() except Full: default (in case of a list you could check its length to avoid catching exception; but Queue's usage cases are typically multi-threading -related so the "check and then get" way is not an option then) Cheers. *j
On Wed, Feb 19, 2014 at 12:16 PM, Jan Kaliszewski
Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py
Excellent! :)
One more minor motivating case:
q = queue.Queue(maxsize=10) ... item = q.get_nowait() except Full: default
(in case of a list you could check its length to avoid catching exception; but Queue's usage cases are typically multi-threading -related so the "check and then get" way is not an option then)
The examples I'm quoting there are from actual real-world code - in this case, *.py in the cpython source tree. Do you have some real-world code that does the above with a try/except, and which is available for viewing and analysis? If so, I'd be happy to add that to the examples! Otherwise, if this is just a contrived/theoretical example, I can still add it someplace, but we already have a good few of those. (BTW, should that be "except Empty"?) ChrisA
19.02.2014 02:20, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 12:16 PM, Jan Kaliszewski
wrote: Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py
Excellent! :)
One more minor motivating case:
q = queue.Queue(maxsize=10) ... item = q.get_nowait() except Full: default
(in case of a list you could check its length to avoid catching exception; but Queue's usage cases are typically multi-threading -related so the "check and then get" way is not an option then)
The examples I'm quoting there are from actual real-world code - in this case, *.py in the cpython source tree. Do you have some real-world code that does the above with a try/except, and which is available for viewing and analysis? If so, I'd be happy to add that to the examples!
Then I was writing that it seemed to me that I encountered such cases in some code I saw not so long ago. But I don't remember the details and indeed am not sure. So -- nevermind. :)
Otherwise, if this is just a contrived/theoretical example, I can still add it someplace, but we already have a good few of those.
(BTW, should that be "except Empty"?)
Yes, it should. Sorry, it's 2:46 *a.m.* here... You just caught my TooLateNightError :) Cheers. *j
Well done, Chris! (Some poor sod does all the hard work while the rest of us chip in our 2c as and when we feel like it, making more work for him. A bit like bear-baiting.) My first thoughts are similar to Paul's: The shorter the examples, the more convincing they are. Dramatically so. And you say "there are a huge number" of them. I think this shows the value of the proposal. Except that I also _love_ the 2-line examples that line up (short or not, mostly not). (I'm a big fan of lining things up, so I will write 2, 3 or 4 statements on a line when they show a consistent pattern. I believe it makes the code much more comprehensible, and much easier to spot typos. Too bad if multiple statements on a line is almost a taboo for the rest of you plonkers - I know I'm right and the rest of the planet is wrong. ) Whereas when the new form is multi-line it seems harder to understand than the original (though this may be partly because the new syntax is simply unfamiliar. Writing the target variable only once is a good thing as far as it goes, but that is not very far). I have to say I'm not keen on the cases where you introduce an extra contraction, simply because it's possible (although the fact that the proposal makes it possible is sort of a point in its favour, in a perverted way). E.g. given try: netloc_enc = netloc.encode("ascii") except UnicodeEncodeError: netloc_enc = netloc.encode("idna") self.putheader('Host', netloc_enc) which would contract (consistently with the simpler examples) to: netloc_enc = netloc.encode("ascii") except UnicodeEncodeError:netloc.encode("idna") self.putheader('Host', netloc_enc) you go one step further and write self.putheader('Host', netloc.encode("ascii") except UnicodeEncodeError: netloc.encode("idna") ) I confess I have the (bad) habit of writing "slick" (i.e. as few statements as possible) code like this myself (probably to try to show how clever I am ). But I know in my heart that it's slightly harder to write and significantly harder to understand and maintain, and the performance advantage _if any_ is negligible. It really is a mistake to put too much content into one statement, and I try to fight my tendency to do it. Thanks again, Rob Cliffe On 18/02/2014 20:55, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 3:06 AM, Chris Angelico
wrote: I'm currently working on finding a bunch of examples from the stdlib that could be translated. Will post them once I get the script sorted out. As it's currently 3AM, though, that means I need to fry up some bacon or something before I continue :) Alright, results are in.
Script: https://github.com/Rosuav/ExceptExpr/blob/master/find_except_expr.py Output: https://github.com/Rosuav/ExceptExpr/blob/master/candidates.txt Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py
The last one is the most readable. I've collected up a bunch of viable candidates for translation. (Note that I'm not advocating going through and editing these files for no other reason. That'd just be code churn. But these are cases where, had this feature existed when that code was written, it could have been taken advantage of.)
My script is currently _very_ simplistic. It looks *only* for assignments, so it won't find something like this:
try: foo.append(args[0]) except IndexError: foo.append('')
which is correct, because it's impossible to know whether foo.append() will raise IndexError. (Chances are it won't, especially if we know foo is a list, for instance.) It's not safe to directly translate that sort of thing. It might, however, be something worth improving, as it narrows the scope of the except clause. But if that same code is written thus:
try: tmp = args[0] except IndexError: tmp = '' foo.append(tmp)
then the script _will_ find it, and then it really is a candidate for editing.
Point to note: Apart from one instance, where it wasn't being used anyway, I found not a single instance of 'as' being used. There was one case where sys.exc_info() was referenced, though, so this may just mean that the stdlib files in question are maintaining compatibility with old versions of Python.
I didn't look at most of the tests. The script found 195 plausible try/except blocks, of which 37 have the word "test" in the name;that leaves 158 that are likely to benefit from this change. There are a couple of false positives, but not many.
Next is to figure out what else is a candidate for editing. Here's my current criteria, straight from the docstring:
"""Report on 'simple' try/except statements.
The definition of simple is: 1. No else or finally clause 2. Only one except clause (currently) 3. Exactly one statement in the try clause 4. Exactly one statement in each except clause 5. Each of those statements is the same type. 6. That type is one that could be an expression. 7. Those statements are all compatible.
The last two are the trickiest. Currently I'm looking only for assignments, where both try and except assign to the same target. This is, however, too narrow."""
Interestingly, removing criterion 2 gives us three additional examples out of the test suite, and nothing else. There are no cases outside of the test suite that look like this:
try: x = some_calculation except ValueError: x = something_else except OverflowError: x = different_thing
(The test suite has one case with those exact two exceptions, and a pair that use OverflowError and ZeroDivisionError. In each case, they're comparing two actions to make sure they give the same result, where "throwing OverflowError" is a result like any other.)
Conclusions: The need for chained exception catching might not be so great after all, and even the 'as' keyword isn't as (heh heh) important as I thought it was.
Alternate conclusion: My sample is too small. Need more. Data, you have the helm.
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/
----- 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:25 PM, Rob Cliffe
Well done, Chris! (Some poor sod does all the hard work while the rest of us chip in our 2c as and when we feel like it, making more work for him. A bit like bear-baiting.)
It may be more work for me, but it's also the validation of that work. This isn't a case of "little me against the throng, I'm only one and possibly I'm wrong" [1] - it's something that's generating several hundred emails' worth of interest. And once it quiets down here, I can post to python-dev and start it all over again :)
The shorter the examples, the more convincing they are. Dramatically so. And you say "there are a huge number" of them. I think this shows the value of the proposal.
Yep. I'd guess that there are over a hundred examples in the stdlib that would become one-liners with this proposal. Quite a few of them have long assignment targets like _CONFIG_VARS['abiflags'] (and there was a longer one, but I don't have it handy), so the expression form encourages DRY.
Too bad if multiple statements on a line is almost a taboo for the rest of you plonkers - I know I'm right and the rest of the planet is wrong.
I'll join you on the 'right' side of the planet :) If putting two statements on a line makes it easier to spot bugs, I'll do it.
I have to say I'm not keen on the cases where you introduce an extra contraction, simply because it's possible (although the fact that the proposal makes it possible is sort of a point in its favour, in a perverted way). E.g. given
try: netloc_enc = netloc.encode("ascii") except UnicodeEncodeError: &nb sp; netloc_enc = netloc.encode("idna") self.putheader('Host', netloc_enc)
which would contract (consistently with the simpler examples) to:
netloc_enc = netloc.encode("ascii") except UnicodeEncodeError: netloc.encode("idna") self.putheader('Host', netloc_enc)
you go one step further and write
self.putheader('Host', netloc.encode("ascii") except UnicodeEncodeError: netloc.encode("idna") )
This is where it gets strongly debatable. Obviously, since this is an expression, it can be used in any context where an expression is valid, not just an assignment. However, part of the issue may be that my script was looking only for assignments. A lot of cases were clearly worth mentioning, some others are doubtful, a number of them just not worth the hassle. I'd like some good non-assignment examples. ChrisA [1] Theresa's song from The Mountebanks: http://math.boisestate.edu/gas/gilbert/plays/mountebanks/webopera/mount_07.h...
On Wed, Feb 19, 2014 at 3:06 AM, Chris Angelico
wrote: I'm currently working on finding a bunch of examples from the stdlib that could be translated. Will post them once I get the script sorted out. As it's currently 3AM, though, that means I need to fry up some bacon or something before I continue :) Alright, results are in.
Script: https://github.com/Rosuav/ExceptExpr/blob/master/find_except_expr.py Output: https://github.com/Rosuav/ExceptExpr/blob/master/candidates.txt Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py I've gone through a very small code base of my own to collect some statistics. Ignoring duplication where a section of code was copied from one program to another,
On 18/02/2014 20:55, Chris Angelico wrote: there were approximately: 15 instances where the new syntax could naturally be used, roughly half were variable assignment, the rest were "return" statements. Of these I judged there were 9 where the new form was short enough or at least readable enough to be a clear improvement. The other 6 were long and complicated enough to be fairly indigestible using either the old or the new syntax. I also found, although I wasn't originally looking for them: - 2 instances where there was a temptation to abuse the new syntax: var = expr except Exception1: ShutDownProgram() - 8 instances where I wanted to do an operation and ignore certain exceptions, this sort of thing: try: dosomething() except ValueError: pass and found I wanted the syntax "dosomething() except ValueError: pass". Under the current proposal this could be achieved by "dosomething() except ValueError: None", but this does not read as naturally. - 13 instances where I wanted to do something that could _not_ be written as an expression and ignore errors, broken down as follows: 5 where I wanted to assign an expression value to a variable but only if evaluating the expression did not raise an error (if it did raise an error I wanted to leave the variable untouched) 1 where I wanted to return the value of an expression, but do nothing (NOT return) if evaluating the expression raised an error. 7 of the form "del x[y] except IndexError: pass" (so to speak) So is there a case for extending the syntax to allow expr except <exception-list>: pass where the expression is used stand-alone (i.e. its value is thrown away). "pass" could be semantically equivalent to "None" (re the interactive interpreter). Or even for allowing "except <exception-list>: pass" after other statements such as "del"? Overall there were 6 instances of an exception list more complicated than a single exception name. There were none of "except Exception1 as ...". So now I am willing to go along with dropping "as" from the PEP; it is something that could always be added later. Best wishes Rob Cliffe _ _
On Wed, Feb 19, 2014 at 06:56:52PM +0000, Rob Cliffe wrote:
I also found, although I wasn't originally looking for them: - 2 instances where there was a temptation to abuse the new syntax: var = expr except Exception1: ShutDownProgram()
Any use of the expect *expression* for purely for side-effects, as opposed to calculating a value, is abuse of the syntax and should be discouraged in the strongest possible terms.
- 8 instances where I wanted to do an operation and ignore certain exceptions, this sort of thing: try: dosomething() except ValueError: pass
This is a statement. It doesn't calculate a result, or if it does, it shoves that result away somewhere else rather than returning it as the result of evaluating an expression.
and found I wanted the syntax "dosomething() except ValueError: pass". Under the current proposal this could be achieved by "dosomething() except ValueError: None", but this does not read as naturally.
This is (1) an abuse of expression syntax, (2) taken literally, impossible, and (3) the fact that it doesn't read the way that you want is an excellent sign that what you want is a bad fit to the expression form. Expressions always evaluate to a value (unless they fail, or the computation never terminates, but let's ignore those cases). While you are free to ignore the calculated value, that's usually a sign that you're doing something wrong. Why calculate a result if you don't care about it? The way to tell if something is an expression or statement is to imagine you were assigning it to a variable. This works: result = x + y - (x*y)/((x**2) + (y**2)) but these doesn't: result = pass result = del variable because they don't return any value. The first case is, as far as the compiler is concerned, exactly equivalent to: result = with the right hand side left blank. So if you are tempted to put something in an except expression that doesn't return a result, or where you don't care about the result: who_cares = do_something() except Exception: pass that's a sign you trying to abuse the syntax.
- 13 instances where I wanted to do something that could _not_ be written as an expression and ignore errors, broken down as follows: 5 where I wanted to assign an expression value to a variable but only if evaluating the expression did not raise an error (if it did raise an error I wanted to leave the variable untouched)
That's easy with an except expression: value = some_expression() except SpamError: value Assigning value = value may seem a bit funny, but not excessively.
1 where I wanted to return the value of an expression, but do nothing (NOT return) if evaluating the expression raised an error.
This relies on side-effects, namely, returning from a function, and as such, has no place in an expression.
7 of the form "del x[y] except IndexError: pass" (so to speak)
More side-effects.
So is there a case for extending the syntax to allow expr except <exception-list>: pass where the expression is used stand-alone (i.e. its value is thrown away). "pass" could be semantically equivalent to "None" (re the interactive interpreter).
-1 That makes the syntax a freakish "neither fish nor fowl nor good red herring" hybrid, sometimes an expression, sometimes a statement, and you won't know which until runtime. That's nasty. -- Steven
On 18/02/2014 15:56, Paul Moore wrote:
On 18 February 2014 15:05, Chris Angelico
wrote: 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 :-) Whether it's a colon or another keyword, _something_ is necessary in there, if it's permissible to specify an exception type:
sum(x[3] except IndexError 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
How would you parse each of those, without a separator? With "return" as a separator. I said that above. Was I not clear?
One option might be to have a separator that's present only when the exception type is listed. For instance:
sum(x[3] except(IndexError) 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
Does that sort of thing have enough support to be added to the PEP? I'm not sure it gives us anything over the colon notation, which has the benefit of being consistent with the statement form (and is stylistically similar to lambda, so it's not grossly inappropriate to an expression context) I remain less than happy with the colon notation, although I will concede that the basic
x[3] except IndexError: 0
form is not horrible - it just becomes horrible very, very fast when people try to do anything more complicated than that. I guess this is a subjective thing, no point in shouting too much about it. But given that this notation is (about) the most compact for its generality, wouldn't any alternative become horrible even faster? Rob Cliffe On the other hand, none of the examples of *any* of the proposed syntaxes look compelling to me - they are either too simple to be realistic, or so complex that they should be factored out into multiple statements, in my view.
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
On 18 February 2014 18:30, Rob Cliffe
I remain less than happy with the colon notation, although I will concede that the basic
x[3] except IndexError: 0
form is not horrible - it just becomes horrible very, very fast when people try to do anything more complicated than that.
I guess this is a subjective thing, no point in shouting too much about it. But given that this notation is (about) the most compact for its generality, wouldn't any alternative become horrible even faster?
Not in my view, because I find keywords more readable than punctuation in this case. But yes, it is subjective. Even to the point that my bleary-eyed, pre-cup-of-tea early morning self has stronger views on the matter than I do right now :-) Paul
On Tue, Feb 18, 2014 at 03:56:10PM +0000, Paul Moore wrote:
On 18 February 2014 15:05, Chris Angelico
wrote: 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 :-)
Whether it's a colon or another keyword, _something_ is necessary in there, if it's permissible to specify an exception type:
sum(x[3] except IndexError 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
How would you parse each of those, without a separator?
With "return" as a separator. I said that above. Was I not clear?
Yes you were clear, but this is a huge complicated thread and if Chris is the least bit like me, he's probably feeling a bit overloaded :-) Using return to not actually return seems completely wrong to me. This is the problem with all the proposals to overload some arbitrary keyword *just because it is available*. To me, all of these are equally as arbitrary and silly: expression except return 42 expression except pass 42 expression except del 42 expression except import 42 expression except class 42 For each keyword in (return, pass, del, import, ...), we are attempting to overload it to mean something different inside an except expression to what it does outside of one. With return and pass, if we squint and turn our heads to the side, we might be able to persuade ourselves that there is a vague analogy between what the keyword does outside of expression to what it does inside ("it returns from the expression, but not from the function" sort of thing). But I don't consider vague analogies to be a good enough reason to overload a keyword with two distinct uses. Is anyone going to propose some other, new, keyword? Although the barrier to new keywords is high, it's not impossibly high. It's worth at least considering.
I remain less than happy with the colon notation, although I will concede that the basic
x[3] except IndexError: 0
form is not horrible - it just becomes horrible very, very fast when people try to do anything more complicated than that.
Really? I think adding a second exception to the same clause is not horrible at all. x[3] except IndexError, KeyError: 0 Parens around the exception list should be permitted, but I don't think they need to be mandatory: x[3] except (IndexError, KeyError): 0 If we allow multiple except clauses -- and I am +1 on that -- then it should be recommended to put one except clause per line, indented for clarity where needed: my_values = [# existing syntax lambda a, b: (a+b)/(a*b), somelist[2:-1], {key: value for key in sequence}, some_function(x) if x else -1, # proposed new syntax (minmax(sequence) except ValueError: (-1, -1)), (str(x[3]) except IndexError, KeyError: 0, except NameError: UNDEF, except UnicodeDecodeError: '?', ), ] As you can see from my examples, using colons is hardly unheard of, and while multiple except clauses does make the except expression more complicated, the use of parens and sensible formatting allows you to digest it a bit at a time. Of course people might abuse this. But they might abuse list comps, generator expressions, ternary if, and even mathematical expressions too. In practice we solve that by slapping them with a halibut :-) and saying "don't do that". -- Steven
On 19/02/2014 00:43, Steven D'Aprano wrote:
On Tue, Feb 18, 2014 at 03:56:10PM +0000, Paul Moore wrote:
On 18 February 2014 15:05, Chris Angelico
wrote: 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 :-) Whether it's a colon or another keyword, _something_ is necessary in there, if it's permissible to specify an exception type:
sum(x[3] except IndexError 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
How would you parse each of those, without a separator? With "return" as a separator. I said that above. Was I not clear? Yes you were clear, but this is a huge complicated thread and if Chris is the least bit like me, he's probably feeling a bit overloaded :-)
Using return to not actually return seems completely wrong to me. This is the problem with all the proposals to overload some arbitrary keyword *just because it is available*. To me, all of these are equally as arbitrary and silly:
expression except return 42 expression except pass 42 expression except del 42 expression except import 42 expression except class 42 I agree. For me, "return" would be the least indigestible - at least it suggests getting a value and parking it somewhere. My support for the
colon is based on (1) its conciseness (2) its natural meaning in my mind, something like "here follows" (3) a somewhat fanatical hankering for consistency - there is a colon at the end of an "except" statement. (Although actually I would prefer that "if", "while", "try", "except" etc. statements _didn't_ require a colon - I still forget to put them in sometimes. Not sure where that leaves me.) > > For each keyword in (return, pass, del, import, ...), we are attempting > to overload it to mean something different inside an except expression > to what it does outside of one. > > With return and pass, if we squint and turn our heads to the side, we > might be able to persuade ourselves that there is a vague analogy > between what the keyword does outside of expression to what it does > inside ("it returns from the expression, but not from the function" sort > of thing). But I don't consider vague analogies to be a good enough > reason to overload a keyword with two distinct uses. > > Is anyone going to propose some other, new, keyword? Although the > barrier to new keywords is high, it's not impossibly high. It's worth at > least considering. I confess I don't understand what the issues are that makes the barrier so high, and I willingly defer to those who know more about it than I do. But if we admit the possibility, "then" is the best candidate I can think of:
x = d[y] except KeyError then z
I remain less than happy with the colon notation, although I will concede that the basic
x[3] except IndexError: 0
form is not horrible - it just becomes horrible very, very fast when people try to do anything more complicated than that. Really? I think adding a second exception to the same clause is not horrible at all.
x[3] except IndexError, KeyError: 0
Parens around the exception list should be permitted, but I don't think they need to be mandatory:
x[3] except (IndexError, KeyError): 0
You strike a chord with me, if only because I spend half my time with a language that requires parentheses round everything, ergo I'm biased. But more seriously, Python generally does not require parentheses unless there's a very good reason.
If we allow multiple except clauses -- and I am +1 on that -- then it should be recommended to put one except clause per line, indented for clarity where needed:
my_values = [# existing syntax lambda a, b: (a+b)/(a*b), somelist[2:-1], {key: value for key in sequence}, some_function(x) if x else -1, # proposed new syntax (minmax(sequence) except ValueError: (-1, -1)), (str(x[3]) except IndexError, KeyError: 0, except NameError: UNDEF, except UnicodeDecodeError: '?', ), ]
As you can see from my examples, using colons is hardly unheard of, and while multiple except clauses does make the except expression more complicated, the use of parens and sensible formatting allows you to digest it a bit at a time.
Of course people might abuse this. But they might abuse list comps, generator expressions, ternary if, and even mathematical expressions too. In practice we solve that by slapping them with a halibut :-) and saying "don't do that".
I pretty much agree with everything you have said in this post. (Kinda nice, as we had a violent disagreement in the past. :-) ) Rob Cliffe
On Wed, Feb 19, 2014 at 12:33 PM, Rob Cliffe
I agree. For me, "return" would be the least indigestible - at least it suggests getting a value and parking it somewhere. My support for the colon is based on (1) its conciseness (2) its natural meaning in my mind, something like "here follows" (3) a somewhat fanatical hankering for consistency - there is a colon at the end of an "except" statement. (Although actually I would prefer that "if", "while", "try", "except" etc. statements didn't require a colon - I still forget to put them in sometimes. Not sure where that leaves me.)
Compare all of these: def f(x): return x + 2 f = lambda x: x + 2 def f(x): try: return x+2 except TypeError: return x f = lambda x: x+2 except TypeError: x In each case, the expression form implicitly "gives back" an expression after a colon, while the statement form implicitly goes and executes the statement after the colon. Does that make reasonable sense? "Here we are executing statements. Here, go execute that suite after that colon." vs "Here we are evaluating an expression. Here, go evaluate that expression after that colon."
I confess I don't understand what the issues are that makes the barrier so high, and I willingly defer to those who know more about it than I do. But if we admit the possibility, "then" is the best candidate I can think of:
x = d[y] except KeyError then z
The biggest barrier to adding a keyword is code that uses that word as an identifier. Imagine if "id" were changed from a built-in function (which can be shadowed) to a keyword (which can't). Every piece of databasing code that stores the primary key in "id" would be broken, as would innumerable other codebases. So the trick is to find something that makes really REALLY good sense in the context it's being used in, and really bad sense as an identifier. Currently, the PEP mentions "then", "use", and "when", and my preference would be in that order. I suspect "then" is unlikely to be used as a name in as many places as "when" will be, but I can't be sure without actually searching the code of the world. ChrisA
18.02.2014 16:56, Paul Moore wrote:
On 18 February 2014 15:05, Chris Angelico
wrote: 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 :-)
Whether it's a colon or another keyword, _something_ is necessary in there, if it's permissible to specify an exception type:
sum(x[3] except IndexError 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
How would you parse each of those, without a separator?
With "return" as a separator. I said that above. Was I not clear?
IMHO it would be an abuse of the keyword. "return" is very strong signal to programmers eyes: "here we exit the body of the function" (and the function must be defined in the statement-based way: by def, not by lambda; return is not used in lambda so in an except expression it would even more alien and out of place).
One option might be to have a separator that's present only when the exception type is listed. For instance:
sum(x[3] except(IndexError) 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
Does that sort of thing have enough support to be added to the PEP? I'm not sure it gives us anything over the colon notation, which has the benefit of being consistent with the statement form (and is stylistically similar to lambda, so it's not grossly inappropriate to an expression context)
I remain less than happy with the colon notation, although I will concede that the basic
x[3] except IndexError: 0
form is not horrible - it just becomes horrible very, very fast when people try to do anything more complicated than that. [snip]
IMHO it'll not become horible if some parens are added... ...either after 'except' (my favorite variant): x[3] except (IndexError: 0) sum(x[3] except (IndexError: 0) for x in list_of_tuples) ...or before 'except': x[3] (except IndexError: 0) sum(x[3] (except IndexError: 0) for x in list_of_tuples) ...or just enclosing the whole expression: (x[3] except IndexError: 0) sum((x[3] except IndexError: 0) for x in list_of_tuples) Cheers. *j
On Wed, Feb 19, 2014 at 11:58 AM, Jan Kaliszewski
sum((x[3] except IndexError: 0) for x in list_of_tuples)
Really, I don't think this example is all that bad even without the parens: sum(x[3] except IndexError: 0 for x in list_of_tuples) Compare: sum(x[3] if len(x)>3 else 0 for x in list_of_tuples) which is a roughly-comparable LBLY check doing the same thing, and which doesn't need parens. If you want 'em, go ahead, put 'em in, but it's not horrible without them. ChrisA
On 2014-02-19 00:58, Jan Kaliszewski wrote:
18.02.2014 16:56, Paul Moore wrote:
On 18 February 2014 15:05, Chris Angelico
wrote: 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 :-)
Whether it's a colon or another keyword, _something_ is necessary in there, if it's permissible to specify an exception type:
sum(x[3] except IndexError 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
How would you parse each of those, without a separator?
With "return" as a separator. I said that above. Was I not clear?
IMHO it would be an abuse of the keyword. "return" is very strong signal to programmers eyes: "here we exit the body of the function" (and the function must be defined in the statement-based way: by def, not by lambda; return is not used in lambda so in an except expression it would even more alien and out of place).
One option might be to have a separator that's present only when the exception type is listed. For instance:
sum(x[3] except(IndexError) 0 for x in list_of_tuples) sum(x[3] except 0 for x in list_of_tuples)
Does that sort of thing have enough support to be added to the PEP? I'm not sure it gives us anything over the colon notation, which has the benefit of being consistent with the statement form (and is stylistically similar to lambda, so it's not grossly inappropriate to an expression context)
I remain less than happy with the colon notation, although I will concede that the basic
x[3] except IndexError: 0
form is not horrible - it just becomes horrible very, very fast when people try to do anything more complicated than that. [snip]
IMHO it'll not become horible if some parens are added...
...either after 'except' (my favorite variant):
x[3] except (IndexError: 0)
sum(x[3] except (IndexError: 0) for x in list_of_tuples)
-1 That would preclude a tuple of exceptions.
...or before 'except':
x[3] (except IndexError: 0)
sum(x[3] (except IndexError: 0) for x in list_of_tuples)
-1 That looks like a call.
...or just enclosing the whole expression:
(x[3] except IndexError: 0)
sum((x[3] except IndexError: 0) for x in list_of_tuples)
No complaints there!
On 18/02/2014 14:07, Paul Moore wrote:
On Wed, Feb 19, 2014 at 12:00 AM, Paul Moore
wrote: On 18 February 2014 12:49, MRAB
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
On 18 February 2014 13:37, Chris Angelico
wrote: 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 We *must* be able to specify the exception(s) to catch. Otherwise the whole thing is pretty useless:
MisspeltVariableName / x except "You divided by zero." # No you didn't, you raised a NameError. 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/6602 - Release Date: 02/17/14
On 18 February 2014 15:27, Rob Cliffe
We must be able to specify the exception(s) to catch. Otherwise the whole thing is pretty useless:
MisspeltVariableName / x except "You divided by zero." # No you didn't, you raised a NameError.
Killer argument. You're right, absolutely. Paul
Paul Moore wrote:
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.
The expression itself doesn't have to be complicated. Even a simple expression such as x[i] can invoke arbitrary code, and therefore raise arbitrary exceptions, including ones that represent bugs and therefore shouldn't be silenced. -- Greg
18.02.2014 22:42, Greg Ewing wrote:
Paul Moore wrote:
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.
The expression itself doesn't have to be complicated. Even a simple expression such as x[i] can invoke arbitrary code, and therefore raise arbitrary exceptions, including ones that represent bugs and therefore shouldn't be silenced.
And including KeyboardInterrupt which, with the default SIGINT handler, can be raised *at any point*, beetween any bytecode instructions (what is a serious trouble for critical with|finally-based cleanups but it's another story). Cheers. *j
On Tue, Feb 18, 2014 at 02:07:43PM +0000, Paul Moore wrote:
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.
But you can't ignore that issue. That issue is *critical* to why it is a simple binary operation. I think I would almost prefer the status quo of no except-expression than one which encourages people to implicitly catch all exceptions. Yes, for a trivially simple case, it's harmless: 1/x except INFINITY but you're teaching people bad habits. It doesn't take a much more complex version to show why it's a bad habit: 1/x + mylits[0] except INFINITY If not for backwards compatibility, I think bare excepts should be removed from the language altogether. They are an attractive nuisance. I don't want to add even more of them. If you truly want to catch everything, catch BaseException. The fact that this is longer to write than Exception is a *good thing*. Making it easier to do the wrong thing is the wrong thing to do. -1 on bare excepts in the expression form. If that makes it harder to sell the PEP, oh well, better that than adding a misfeature to the language. -- Steven
On 18/02/2014 23:43, Ethan Furman wrote:
On 02/18/2014 03:27 PM, Steven D'Aprano wrote:
-1 on bare excepts in the expression form.
Agreed.
-- ~Ethan~ I'm prepared to concede this point (as if my opinion as a very junior member of the Python community counted for much :-) ). Nick Coghlan and Steven D'Aprano made a very strong case in their posts. My only real objection is that I would like consistency between "except" statements and "except" expressions. But I suspect that you (plural), if you had the chance to rewrite history, would also want to disallow a bare "except" statement. (Would you?) Nick, Steven, Ethan (and anybody else), are you willing to answer this question? No offence intended, but I'm curious. No, I should be honest: I'm hoping to score a debating point. Best wishes, 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 Wed, Feb 19, 2014 at 1:04 PM, Jan Kaliszewski
Me too -- *except* the bare except syntax which, in case of except expression, would be a serious nuisance without any real advantage. (IMHO)
Jan, and everyone else who's expressed opinions on the use of bare except: https://raw2.github.com/Rosuav/ExceptExpr/master/pep-0463.txt (note that the file name and URL changed when a PEP number was assigned) I've added two sections, one in "Open Issues" and the other in "Rejected sub-proposals", on this topic. Have I covered the salient points? ChrisA
19.02.2014 03:53, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 1:04 PM, Jan Kaliszewski
wrote: Me too -- *except* the bare except syntax which, in case of except expression, would be a serious nuisance without any real advantage. (IMHO)
Jan, and everyone else who's expressed opinions on the use of bare except:
https://raw2.github.com/Rosuav/ExceptExpr/master/pep-0463.txt
(note that the file name and URL changed when a PEP number was assigned)
I've added two sections, one in "Open Issues" and the other in "Rejected sub-proposals", on this topic. Have I covered the salient points?
Excellent. :) *j
On Wed, Feb 19, 2014 at 01:52:30AM +0000, Rob Cliffe wrote:
On 18/02/2014 23:43, Ethan Furman wrote:
On 02/18/2014 03:27 PM, Steven D'Aprano wrote:
-1 on bare excepts in the expression form.
Agreed.
-- ~Ethan~ I'm prepared to concede this point (as if my opinion as a very junior member of the Python community counted for much :-) ). Nick Coghlan and Steven D'Aprano made a very strong case in their posts. My only real objection is that I would like consistency between "except" statements and "except" expressions.
But I suspect that you (plural), if you had the chance to rewrite history, would also want to disallow a bare "except" statement. (Would you?) Nick, Steven, Ethan (and anybody else), are you willing to answer this question? No offence intended, but I'm curious. No, I should be honest: I'm hoping to score a debating point.
No offence taken. I think bare excepts are a bug magnet and violate "Explicit is better than implicit", and I would like to see them gone, even though they do have a use at the interactive interpreter for lazy people. Including me. py> try: this() ... except: None Yes, I sometimes use that sort of quick-and-dirty construction, but if the language didn't have it, I wouldn't mind writing Exception after the except. Well, maybe a tiny bit. But not enough to complain. I think bare excepts were useful back in ancient days when you could raise strings, and they were caught by identity not value. But I think there is strong evidence that this was a mistake: raising strings was removed in Python 2.6, rather than waiting for Python 3000. I expect that most people don't even remember that you ever could write things like raise "This is an error" and thank goodness for that :-) -- Steven
On Wed, Feb 19, 2014 at 2:25 PM, Steven D'Aprano
I think bare excepts were useful back in ancient days when you could raise strings, and they were caught by identity not value. But I think there is strong evidence that this was a mistake: raising strings was removed in Python 2.6, rather than waiting for Python 3000. I expect that most people don't even remember that you ever could write things like
raise "This is an error"
and thank goodness for that :-)
They were caught by identity? Ouch. Definite bug magnet. In that case, yeah, you'd normally want to just put a bare "except:" and then decide what to do with the exception... only... a bare except can't capture the thing thrown, so how do you decide? IMO having to turn to sys.exc_info is wrong; you should be able to work within the construct of the try/except block. ChrisA
From: Chris Angelico
On Wed, Feb 19, 2014 at 2:25 PM, Steven D'Aprano
wrote: I think bare excepts were useful back in ancient days when you could raise strings, and they were caught by identity not value. But I think there is strong evidence that this was a mistake: raising strings was removed in Python 2.6, rather than waiting for Python 3000. I expect that most people don't even remember that you ever could write things like
raise "This is an error"
and thank goodness for that :-)
They were caught by identity? Ouch. Definite bug magnet. In that case, yeah, you'd normally want to just put a bare "except:" and then decide what to do with the exception... only... a bare except can't capture the thing thrown, so how do you decide? IMO having to turn to sys.exc_info is wrong; you should be able to work within the construct of the try/except block.
As far as I know, wrong or not, that's how you had to do it. I worked on at least one project that actually used a bare except, fished the string out of exc_info, and then did regexp matching on it. Because some people are so clever they're stupid. But anyway, string exceptions are long gone, and good riddance. If you really want the same functionality, you can do it much cleaner by just defining a trivial StringException class, doing raise StringException("This is an error"), and using "except StringException as e" and fishing the string out of e. Only a little bit more verbose than a bare except plus exc_info, and a lot cleaner and more readable…
In the interests of a larger sample size, I've enhanced my "simple try/except clause finder" script to locate the following: 1) Assignments to the same "thing" (which might be a simple name, an attribute, an item, whatever) 2) Augmented assignments, as long as they use the same operator 3) Return values 4) Expressions The last one gets a *lot* of false positives. As far as the AST is concerned, both of these are try/except blocks with a simple expression in each: try: big.complex.function.call(int(x)) except ValueError: big.complex.function.call(x) # and # try: some_file.write(some_data) except IOError: print("Unable to write data", file=sys.stderr) The former might well want to be changed (though I've put those into my "semantic changes" section, see below), but the latter definitely can't. For the moment, I'm not trying to make the script recognize those, so the human has to skim over all of them. But the other three are fairly useful. Once again, there are a good number of try/except blocks that match these fairly restrictive rules - around 300. (THIS! IS! PYTHON!) And once again, they overwhelmingly *do not* use the 'as' clause. For reference, I had the script collect stats on except clauses overall. The same files contain 4617 except clauses, of which 1007 use 'as'. Based on that approximate one-in-five ratio, I would expect there to be about 60 uses of 'as' in the simple try/excepts; but this is not the case. In fact, there are only 34 out of 301, roughly half the proportion required (note that this is skipping all files with "/test/" in their names; including those gives 52/390 which is still way lower than 1/5); and skipping all the two-expression blocks (most of which are false positives) brings it down to just 7 out of 235. Seven. That's how few simple try/except clauses use 'as'. So, I'm really needing someone's big Python project to run this on, to get an alternative data set. Someone want to help out with stats? Annotated examples: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py All files: https://github.com/Rosuav/ExceptExpr/ ChrisA
On Wed, Feb 19, 2014 at 5:10 PM, Chris Angelico
Once again, there are a good number of try/except blocks that match these fairly restrictive rules - around 300. (THIS! IS! PYTHON!) And once again, they overwhelmingly *do not* use the 'as' clause.
Despite the numbers saying that 'as' is almost completely unnecessary to the proposal, I'm leaving it in for the moment, pending more data. However, I'm removing two parts of the proposal: bare except clauses (use "except BaseException:" if you really want that) and chaining of excepts (use parens, "(expr except Exception1: default1) except Exception2: default2"). Current PEP draft: https://raw2.github.com/Rosuav/ExceptExpr/master/pep-0463.txt ChrisA
On Wed, Feb 19, 2014 at 06:06:05PM +1100, Chris Angelico wrote:
Despite the numbers saying that 'as' is almost completely unnecessary to the proposal, I'm leaving it in for the moment, pending more data. However, I'm removing two parts of the proposal: bare except clauses (use "except BaseException:" if you really want that) and chaining of excepts (use parens, "(expr except Exception1: default1) except Exception2: default2").
I think it will be helpful to include a section with a few definitions, because the term "chaining" is ambiguous. It could mean either: * Multiple except clauses in a single expression (that is, n-ary operator, for n > ). Example: (expression except SpamError: spam, except EggsError: eggs) where the 5 operands here are - expression - SpamError - EggsError - spam - eggs Or chaining could mean wrapping one except-expression inside another, such as: (expression except SpamError: spam) except EggsError: eggs which has two expressions each with three operands: - expression - SpamError - spam and - (expression except SpamError: spam) - EggsError - eggs I think it is important to decide which one is chaining, and which one is not, include it in the PEP as a definition, and stick with it. I *strongly* suggest that chaining means the second case. That's what chaining means when we talk about method chaining: obj.spam().eggs() # call eggs method of the result returned by spam and exception chaining: raise ThisError from some_exception I don't think that we should prohibit passing one except-expression as argument to another. The first case, the n-ary form, can be deferred for later. But I don't think that's chaining. It is also important to note that this are not, in general, the same thing! -- Steven
On Wed, Feb 19, 2014 at 7:14 PM, Steven D'Aprano
I think it will be helpful to include a section with a few definitions, because the term "chaining" is ambiguous. It could mean either:
* Multiple except clauses in a single expression (that is, n-ary operator, for n > ). Example:
(expression except SpamError: spam, except EggsError: eggs)
where the 5 operands here are
- expression - SpamError - EggsError - spam - eggs
Or chaining could mean wrapping one except-expression inside another, such as:
(expression except SpamError: spam) except EggsError: eggs
which has two expressions each with three operands:
- expression - SpamError - spam
and
- (expression except SpamError: spam) - EggsError - eggs
I think it is important to decide which one is chaining, and which one is not, include it in the PEP as a definition, and stick with it. I *strongly* suggest that chaining means the second case.
I've been using it to mean the first case. The second case is simply what happens when you use an except-expression as an operand to another except-expression, and I'd be inclined to call that "nesting", as it's effectively this: try: try: _ = expression except SpamError: _ = spam except EggsError: _ = eggs But if that form is called "chaining", then what's the other form called? Whatever it is, I need a word for it, because that one is the one that entails additional syntax.
That's what chaining means when we talk about method chaining:
obj.spam().eggs() # call eggs method of the result returned by spam
and exception chaining:
raise ThisError from some_exception
But not comparison chaining, which is this: 1 < x < 2 I'm okay with changing the word, but I need something to change it _to_. I can't just 'del chaining' - without some other reference, the whole concept would be garbage collected :)
I don't think that we should prohibit passing one except-expression as argument to another.
Of course not, any more than anyone prohibits the nesting of try blocks.
The first case, the n-ary form, can be deferred for later. But I don't think that's chaining.
It is also important to note that this are not, in general, the same thing!
Indeed, they're not. ChrisA
On Wed, Feb 19, 2014 at 07:29:00PM +1100, Chris Angelico wrote:
On Wed, Feb 19, 2014 at 7:14 PM, Steven D'Aprano
wrote: I think it will be helpful to include a section with a few definitions, because the term "chaining" is ambiguous. It could mean either:
* Multiple except clauses in a single expression [...] Or chaining could mean wrapping one except-expression inside another, such as:
(expression except SpamError: spam) except EggsError: eggs
which has two expressions each with three operands [...] I think it is important to decide which one is chaining, and which one is not, include it in the PEP as a definition, and stick with it. I *strongly* suggest that chaining means the second case.
I've been using it to mean the first case. The second case is simply what happens when you use an except-expression as an operand to another except-expression, and I'd be inclined to call that "nesting",
Nested expressions is also a good term for it. [...]
But if that form is called "chaining", then what's the other form called? Whatever it is, I need a word for it, because that one is the one that entails additional syntax.
I don't think it needs a single word, especially since it's being deferred for later. I'm partial to describing it as "multiple except clauses", since that's exactly what it is. As far as I can tell, nobody refers to this as exception chaining: try: this except A: a except B: b except C: c ... Exception chaining is something very different, "raise A from B", and I think it is harmful for comprehension to have "exception chaining" and "chained excepts" mean radically different things.
I'm okay with changing the word, but I need something to change it _to_. I can't just 'del chaining' - without some other reference, the whole concept would be garbage collected :)
Yes, but it doesn't have to be a single word. -- Steven
On 19 February 2014 10:01, Steven D'Aprano
I don't think it needs a single word, especially since it's being deferred for later. I'm partial to describing it as "multiple except clauses", since that's exactly what it is.
As far as I can tell, nobody refers to this as exception chaining:
try: this except A: a except B: b except C: c ...
Exception chaining is something very different, "raise A from B", and I think it is harmful for comprehension to have "exception chaining" and "chained excepts" mean radically different things.
Agreed the terminology should be clarified. In expression terms, a < b < c is referred to as chained comparisons, so whichever way the term "chaining" is used will be surprising to someone... Paul
From: Steven D'Aprano
On Wed, Feb 19, 2014 at 06:06:05PM +1100, Chris Angelico wrote:
Despite the numbers saying that 'as' is almost completely unnecessary to the proposal, I'm leaving it in for the moment, pending more data. However, I'm removing two parts of the proposal: bare except clauses (use "except BaseException:" if you really want that) and chaining of excepts (use parens, "(expr except Exception1: default1) except Exception2: default2").
I think it will be helpful to include a section with a few definitions, because the term "chaining" is ambiguous. It could mean either:
* Multiple except clauses in a single expression (that is, n-ary operator, for n > ). Example:
(expression except SpamError: spam, except EggsError: eggs)
where the 5 operands here are
- expression - SpamError - EggsError - spam - eggs
Or chaining could mean wrapping one except-expression inside another, such as:
(expression except SpamError: spam) except EggsError: eggs
which has two expressions each with three operands:
- expression - SpamError - spam
and
- (expression except SpamError: spam) - EggsError - eggs
I think it is important to decide which one is chaining, and which one is not, include it in the PEP as a definition, and stick with it. I *strongly* suggest that chaining means the second case. That's what chaining means when we talk about method chaining:
obj.spam().eggs() # call eggs method of the result returned by spam
and exception chaining:
raise ThisError from some_exception
I don't think that we should prohibit passing one except-expression as argument to another.
The first case, the n-ary form, can be deferred for later. But I don't think that's chaining.
It is also important to note that this are not, in general, the same thing!
I think it's better to just avoid the word "chaining" entirely in the PEP, for the same reason it's worth avoiding the n-ary form in the syntax. The reason for banning the n-ary form is that it's ambiguous (to humans—obviously the parser could be trivially coded to pick one interpretation or the other) whether it's two excepts on a single try expression, or one except expression inside another. To some people the first interpretation seems like the obvious way to read it, the only one consistent with the rest of Python—but there have been people in this discussion who disagree, and surely that will be even more of a problem with novices. The fact that they are not the same thing _in general_, but _often_ have the same effect anyway, just increases the potential for confusion. The term "chaining" is ambiguous in the same way. To many people who see the first interpretation, "chaining" obviously means the second one. But there are obviously people who disagree—including the author of the PEP—and surely that will again be even more of a problem with novices. So, instead of introducing a potentially confusing term and then defining it, why not just avoid the term? Explain that the n-ary form with multiple except clauses is not allowed (for now); if you want that, _sometimes_ you can use an except expression inside another except expression, but sometimes you will have to rewrite your expression or use the statement form instead. The PEP could even give simple examples of when the substitution does and doesn't work.
On Thu, Feb 20, 2014 at 8:26 AM, Andrew Barnert
I think it's better to just avoid the word "chaining" entirely in the PEP, for the same reason it's worth avoiding the n-ary form in the syntax.
Current version of the PEP lacks the word "chain" in any form. There's a section on "Multiple except clauses" (in the "Deferred sub-proposals" section), that's all. https://raw2.github.com/Rosuav/ExceptExpr/master/pep-0463.txt ChrisA
On Tue, Feb 18, 2014 at 11:06 PM, Chris Angelico
However, I'm removing two parts of the proposal: ... chaining of
excepts (use parens, "(expr except Exception1: default1) except
Exception2: default2").
There are two problems with trying to leave this out. First, you say "use parens" but no use of parenthesis can have the same effect. The alternative you give: (expr except E1: default1) except E2: default2 is not the same as the chained form. This catches E2 in both the evaluation of E1 *and* the evaluation of default1 while the chained form catches both exceptions only in the evaluation of E1. And putting parens anywhere else doesn't do it either. Second, isn't an except expression like any other expression which can be used anywhere an expression can be? So that means that writing something that looks like a chained expression without parenthesis *will* be a valid expression and will have to have some interpretation (depending on whether except is left associative or right associative. Given that, I think it is better to define it as chained now rather than leaving people to think it's chained and never being able to add it in the future (since that will change the semantics of existing code). This is analogous to handling a < b < c: it's a bug magnet in C for inexperienced programmers but can't be changed without breaking code. Starting from scratch in Python, it could be chained. --- Bruce Learn how hackers think: http://j.mp/gruyere-security
On Wed, Feb 19, 2014 at 7:23 PM, Bruce Leban
On Tue, Feb 18, 2014 at 11:06 PM, Chris Angelico
wrote: However, I'm removing two parts of the proposal: ... chaining of
excepts (use parens, "(expr except Exception1: default1) except Exception2: default2").
There are two problems with trying to leave this out.
First, you say "use parens" but no use of parenthesis can have the same effect. The alternative you give: (expr except E1: default1) except E2: default2 is not the same as the chained form. This catches E2 in both the evaluation of E1 and the evaluation of default1 while the chained form catches both exceptions only in the evaluation of E1. And putting parens anywhere else doesn't do it either.
Correct.
Second, isn't an except expression like any other expression which can be used anywhere an expression can be? So that means that writing something that looks like a chained expression without parenthesis will be a valid expression and will have to have some interpretation (depending on whether except is left associative or right associative.
Not quite; it could be made a SyntaxError to abut two except expressions without parens. Failing that, the without-parens form would always mean nesting (or what Steven calls chaining), and a future development that puts a single try with multiple excepts would have to be done with commas. But there's still one big question: What use-case have you for the multi-catch form? (Maybe call it "continued"? "extended"??) I went through the standard library and found pretty much nothing that even tripped my checks, and those were sufficiently borderline that they could just be left as statements. Do you have a good-sized Python codebase that you could either point me to, or run my little finder script on? I want to see more code that would benefit from this, and to see if there are any current use-cases that need multiple except clauses. I'd be looking for something like this: try: foo = expression except SomeException: foo = another_expression except SomeOtherException: foo = some_other_expression Any real-world examples would be appreciated. Even if that sub-proposal doesn't get accepted, it'd still be improved by examples. ChrisA
On 19 February 2014 18:39, Chris Angelico
On Wed, Feb 19, 2014 at 7:23 PM, Bruce Leban
wrote: Second, isn't an except expression like any other expression which can be used anywhere an expression can be? So that means that writing something that looks like a chained expression without parenthesis will be a valid expression and will have to have some interpretation (depending on whether except is left associative or right associative.
Not quite; it could be made a SyntaxError to abut two except expressions without parens. Failing that, the without-parens form would always mean nesting (or what Steven calls chaining), and a future development that puts a single try with multiple excepts would have to be done with commas.
But there's still one big question: What use-case have you for the multi-catch form? (Maybe call it "continued"? "extended"??) I went through the standard library and found pretty much nothing that even tripped my checks, and those were sufficiently borderline that they could just be left as statements.
There's a different way of addressing that concern: applying the same syntactic restriction as is used for generator expressions, and *always* require the parentheses. If using the ":" delimited syntax, then the requirement would be unconditional, if using some other separator (like "return" or "->"), then the parentheses could be optional when the expression is the sole argument to a function call. If someone later makes the case for allowing multiple except clauses, then that's OK, since the parentheses to disambiguate are already guaranteed to be present, regardless of context. Either way, based on your survey of the stdlib, I agree with MAL's suggestion to also rule out embedded name binding. The example you give in the PEP of losing the distinction between whether an iterator yielded a new incremental value or terminated and returned a final value is another sign that allowing name binding is a potential bug magnet, because it conflates two different return channels into one. That's a very different thing from substituting in a known *default* value when the operation fails, and definitely not a practice we want to encourage. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Wed, Feb 19, 2014 at 10:28:59PM +1000, Nick Coghlan wrote:
Either way, based on your survey of the stdlib, I agree with MAL's suggestion to also rule out embedded name binding. The example you give in the PEP of losing the distinction between whether an iterator yielded a new incremental value or terminated and returned a final value is another sign that allowing name binding is a potential bug magnet, because it conflates two different return channels into one.
Hmmm, I think that's a bit strong. If combining the iterator's yield value and the iterator's return value is a mistake, that's not the syntax that makes it a mistake, it's that the very idea of doing so is wrong. We wouldn't say that try...except statements are a bug magnet because we can write: try: result = next(it) except StopIteration as e: result = e.args[0] I had a use-case for naming the exception caught: you are working with some library that returns and expects 2-tuples of (success, value). So you might do this: try: return (True, expr) except SomeError as e: return (False, e.args[0]) which becomes: (True, expr) except SomeError as e: (False, e.args[0]) Chris suggested that there is an example of this in the imap module, but it hasn't hit the PEP yet. -- Steven
On Thu, Feb 20, 2014 at 12:39 AM, Steven D'Aprano
Chris suggested that there is an example of this in the imap module, but it hasn't hit the PEP yet.
Most of the examples are out in a separate file. I'm not sure which of them, if any, should be blessed with a mention in the PEP itself. https://github.com/Rosuav/ExceptExpr/blob/master/examples.py#L356 Part of the mess with that one is that it really needs an author's eye; I can show that this could be done cleanly with the new syntax, but I can't make it both clean *and* trivially provably performing the same job. It'd probably want some other way of rendering the exception to text. ChrisA
On Thu, Feb 20, 2014 at 12:39 AM, Steven D'Aprano
On Wed, Feb 19, 2014 at 10:28:59PM +1000, Nick Coghlan wrote:
Either way, based on your survey of the stdlib, I agree with MAL's suggestion to also rule out embedded name binding. The example you give in the PEP of losing the distinction between whether an iterator yielded a new incremental value or terminated and returned a final value is another sign that allowing name binding is a potential bug magnet, because it conflates two different return channels into one.
Hmmm, I think that's a bit strong. If combining the iterator's yield value and the iterator's return value is a mistake, that's not the syntax that makes it a mistake, it's that the very idea of doing so is wrong.
And by the way, I'd say that it's wrong *in the general sense* to conflate yield/return, but such conflations happen all the time. We can use 'or' to turn any falsy value into some specific thing: def foo(widget, n=None): """Frob the widget n times, or once for each spam""" for i in range(n or len(widget.spam)): widget.frob() Nobody would ever suggest that 0, None, [], and "" should all be treated the same everywhere, but pass any of them to this function and it'll do the same thing. By moving the conflation as far outward as possible, we maintain the distinction as long as possible. At the point where the programmer knows that the two channels must mean the same thing, they can get merged. It's like merging stdout and stderr of a process prior to giving the text to a human. ChrisA
On 19 February 2014 23:52, Chris Angelico
On Thu, Feb 20, 2014 at 12:39 AM, Steven D'Aprano
wrote: On Wed, Feb 19, 2014 at 10:28:59PM +1000, Nick Coghlan wrote:
Either way, based on your survey of the stdlib, I agree with MAL's suggestion to also rule out embedded name binding. The example you give in the PEP of losing the distinction between whether an iterator yielded a new incremental value or terminated and returned a final value is another sign that allowing name binding is a potential bug magnet, because it conflates two different return channels into one.
Hmmm, I think that's a bit strong. If combining the iterator's yield value and the iterator's return value is a mistake, that's not the syntax that makes it a mistake, it's that the very idea of doing so is wrong.
And by the way, I'd say that it's wrong *in the general sense* to conflate yield/return, but such conflations happen all the time. We can use 'or' to turn any falsy value into some specific thing:
def foo(widget, n=None): """Frob the widget n times, or once for each spam""" for i in range(n or len(widget.spam)): widget.frob()
Nobody would ever suggest that 0, None, [], and "" should all be treated the same everywhere, but pass any of them to this function and it'll do the same thing.
By moving the conflation as far outward as possible, we maintain the distinction as long as possible. At the point where the programmer knows that the two channels must mean the same thing, they can get merged. It's like merging stdout and stderr of a process prior to giving the text to a human.
Right, but people can still use the statement form or a context manager to help handle that. We don't need to introduce a quaternary expression with complex scoping issues for it. Since dropping the optional "as" clause drastically simplifies the proposal (no need for a hidden scope), and the use case for it isn't at all compelling, that puts it in the "don't bother" category for me. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Thu, Feb 20, 2014 at 1:03 AM, Nick Coghlan
Since dropping the optional "as" clause drastically simplifies the proposal (no need for a hidden scope), and the use case for it isn't at all compelling, that puts it in the "don't bother" category for me.
In the light of comments like this, I've deferred the 'as' clause, and also everything involving multiple except clauses. The proposal is now simply: expr except exception_list: default with straight-forward semantics. It can easily be extended in future. I've also added a whole bunch of examples, lifted from the separate examples.py. Note that the word "chain" no longer appears in the proposal; it's simply "multiple except clauses". Having a term for it is more useful when it's a part of the proposal; since it's deferred anyway, that's not a big deal. Latest version, as always, is here: https://raw2.github.com/Rosuav/ExceptExpr/master/pep-0463.txt Additional examples, with syntax highlighting: https://github.com/Rosuav/ExceptExpr/blob/master/examples.py Please let me know if I've made any glaring errors (even trivial ones like typos). Lots of editing has happened recently, and several times I discovered that I'd missed an 'as' somewhere or something like that. Chances are I've missed more. To everyone who's already submitted ideas: Thank you! Don't stop! :) ChrisA
On 19/02/2014 08:23, Bruce Leban wrote:
On Tue, Feb 18, 2014 at 11:06 PM, Chris Angelico
mailto:rosuav@gmail.com> wrote: However, I'm removing two parts of the proposal: ... chaining of
excepts (use parens, "(expr except Exception1: default1) except Exception2: default2").
There are two problems with trying to leave this out.
First, you say "use parens" but no use of parenthesis can have the same effect. The alternative you give: (expr except E1: default1) except E2: default2 is not the same as the chained form. This catches E2 in both the evaluation of E1 *_and_* the evaluation of default1 while the chained form catches both exceptions only in the evaluation of E1. And putting parens anywhere else doesn't do it either.
Second, isn't an except expression like any other expression which can be used anywhere an expression can be? So that means that writing something that looks like a chained expression without parenthesis _*will*_ be a valid expression and will have to have some interpretation (depending on whether except is left associative or right associative.
+1. I now see that the semantics of expr1 except E1: (default1 except E2: default2) and (expr1 except E1: default1) except E2: default2 are not the same (in an earlier post I thought they were). And neither of them are the same as: Evaluate expr1 and see what exception it raises: If it raises E1, return default1 If it raises E2, return default2. (To spell it out, as much for my own benefit as anything else: The first catches E2 in the evaluation of default1. The second catches E2 in the evaluation of expr1 OR default1. The last catches E2 in the evaluation of expr1 only. ) I suggest the form without brackets should (be legal and) mean the last of these, because (1) It feels to me like the natural meaning (2) It can't conveniently be expressed otherwise without introducing additional syntax. Rob Cliffe
Given that, I think it is better to define it as chained now rather than leaving people to think it's chained and never being able to add it in the future (since that will change the semantics of existing code). This is analogous to handling a < b < c: it's a bug magnet in C for inexperienced programmers but can't be changed without breaking code. Starting from scratch in Python, it could be chained.
--- Bruce Learn how hackers think: http://j.mp/gruyere-security
_______________________________________________ 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/6606 - Release Date: 02/19/14
On 19/02/2014 10:46, Rob Cliffe wrote:
On 19/02/2014 08:23, Bruce Leban wrote:
On Tue, Feb 18, 2014 at 11:06 PM, Chris Angelico
mailto:rosuav@gmail.com> wrote: However, I'm removing two parts of the proposal: ... chaining of
excepts (use parens, "(expr except Exception1: default1) except Exception2: default2").
There are two problems with trying to leave this out.
First, you say "use parens" but no use of parenthesis can have the same effect. The alternative you give: (expr except E1: default1) except E2: default2 is not the same as the chained form. This catches E2 in both the evaluation of E1 *_and_* the evaluation of default1 while the chained form catches both exceptions only in the evaluation of E1. And putting parens anywhere else doesn't do it either.
Second, isn't an except expression like any other expression which can be used anywhere an expression can be? So that means that writing something that looks like a chained expression without parenthesis _*will*_ be a valid expression and will have to have some interpretation (depending on whether except is left associative or right associative.
+1. I now see that the semantics of
expr1 except E1: (default1 except E2: default2)
and
(expr1 except E1: default1) except E2: default2
are not the same (in an earlier post I thought they were).
And neither of them are the same as:
Evaluate expr1 and see what exception it raises: If it raises E1, return default1 If it raises E2, return default2.
(To spell it out, as much for my own benefit as anything else: The first catches E2 in the evaluation of default1. The second catches E2 in the evaluation of expr1 OR default1. The last catches E2 in the evaluation of expr1 only. ) I suggest the form without brackets should (be legal and) mean the last of these, because (1) It feels to me like the natural meaning (2) It can't conveniently be expressed otherwise without introducing additional syntax.
Rob Cliffe
Also (3) When assigned to a variable it is equivalent to try: var = expr1 except E1: var = default1 except E2: var = default2
Given that, I think it is better to define it as chained now rather than leaving people to think it's chained and never being able to add it in the future (since that will change the semantics of existing code). This is analogous to handling a < b < c: it's a bug magnet in C for inexperienced programmers but can't be changed without breaking code. Starting from scratch in Python, it could be chained.
--- Bruce Learn how hackers think: http://j.mp/gruyere-security
_______________________________________________ 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/6606 - Release Date: 02/19/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/
No virus found in this message. Checked by AVG - www.avg.com http://www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6606 - Release Date: 02/19/14
On 19 February 2014 07:06, Chris Angelico
On Wed, Feb 19, 2014 at 5:10 PM, Chris Angelico
wrote: Current PEP draft:
https://raw2.github.com/Rosuav/ExceptExpr/master/pep-0463.txt
It looks good to me. I think I most often use the ternary if/else as part of a larger expression such as a dict display. I can imagine that the same would apply here and it might be good to add an example of this. I don't know how common a pattern it is in other people's code but something like: header = { 'param1': foo, 'param2': bar, ... } try: header['paramN'] = load_from_file(baz, spam) except OSError: header['paramN'] = '' In this case your proposed syntax would make it possible to write the whole thing as a single expression. Without the syntax you'd probably need to write an additional function to get the same effect: def load_from_file_default_None(baz, spam): try: return load_from_file(baz, spam) except OSError: return None Oscar
On Wed, Feb 19, 2014 at 11:57 PM, Oscar Benjamin
I don't know how common a pattern it is in other people's code but something like:
header = { 'param1': foo, 'param2': bar, ... }
try: header['paramN'] = load_from_file(baz, spam) except OSError: header['paramN'] = ''
That looks awesome! Do you have actual production code that looks like that, and if so, can I have a squiz at it? Because that's a strong use-case right there. What *isn't* a clear use-case is where you want the header to not exist at all if the exception's thrown. For that, it's better to use "try: assign; except OSError: pass", because there's no easy way to tell a dict to not set something. It might be nice if it could be done, but it can't. For that reason I've sometimes stipulated that a list or dict is allowed to have some kind of shim in it, resulting in code like this: lst = [ fn1 and load_from_file(fn1), fn2 and load_from_file(fn2), load_from_constant("spam spam spam", # ... etc ... ] But if you specifically want those empty strings anyway, then yes, except expressions would be perfect. ChrisA
On 19 February 2014 13:30, Chris Angelico
On Wed, Feb 19, 2014 at 11:57 PM, Oscar Benjamin
wrote: I don't know how common a pattern it is in other people's code but something like:
header = { 'param1': foo, 'param2': bar, ... }
try: header['paramN'] = load_from_file(baz, spam) except OSError: header['paramN'] = ''
That looks awesome! Do you have actual production code that looks like that, and if so, can I have a squiz at it? Because that's a strong use-case right there.
I can't really find anything like that right now. I was just thinking that there are basically two situations where I like to use ternary if/else: as part of a larger expression/display or in a comprehension/generator expression. This proposal is similar but it's hard to find the cases that would be simplified by it as they will probably be written in a very different way right now. Oscar
On Thu, Feb 20, 2014 at 1:48 AM, Oscar Benjamin
This proposal is similar but it's hard to find the cases that would be simplified by it as they will probably be written in a very different way right now.
If they're written the way you describe, with a "try" block that assigns to something and an "except" block that assigns to the exact same thing, my script will find them. You might want to disable (by commenting out) some of the handlers at the top; the ast.Expr one, in particular, tends to generate a lot of spam. https://github.com/Rosuav/ExceptExpr/blob/master/find_except_expr.py ChrisA
On 19 February 2014 15:06, Chris Angelico
On Thu, Feb 20, 2014 at 1:48 AM, Oscar Benjamin
wrote: This proposal is similar but it's hard to find the cases that would be simplified by it as they will probably be written in a very different way right now.
If they're written the way you describe, with a "try" block that assigns to something and an "except" block that assigns to the exact same thing, my script will find them. You might want to disable (by commenting out) some of the handlers at the top; the ast.Expr one, in particular, tends to generate a lot of spam.
A search through cpython didn't turn up much. There are only two Subscript nodes found by this script and only this one seems relevant: http://hg.python.org/cpython/file/9cfb3d1cf0d1/Lib/sysconfig.py#l514 For whatever reason that code just creates a dict with a whole load of assignments anyway though. Personally I would have written that as a display so instead of this: _CONFIG_VARS = {} # Normalized versions of prefix and exec_prefix are handy to have; # in fact, these are the standard versions used most places in the # Distutils. _CONFIG_VARS['prefix'] = _PREFIX _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX _CONFIG_VARS['py_version'] = _PY_VERSION _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT _CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2] _CONFIG_VARS['installed_base'] = _BASE_PREFIX _CONFIG_VARS['base'] = _PREFIX _CONFIG_VARS['installed_platbase'] = _BASE_EXEC_PREFIX _CONFIG_VARS['platbase'] = _EXEC_PREFIX _CONFIG_VARS['projectbase'] = _PROJECT_BASE try: _CONFIG_VARS['abiflags'] = sys.abiflags except AttributeError: # sys.abiflags may not be defined on all platforms. _CONFIG_VARS['abiflags'] = '' You could do this: _CONFIG_VARS = { # Normalized versions of prefix and exec_prefix are handy to have; # in fact, these are the standard versions used most places in the # Distutils. 'prefix':_PREFIX, 'exec_prefix':_EXEC_PREFIX, 'py_version':_PY_VERSION, 'py_version_short':_PY_VERSION_SHORT, 'py_version_nodot':_PY_VERSION[0] + _PY_VERSION[2], 'installed_base':_BASE_PREFIX, 'base':_PREFIX, 'installed_platbase':_BASE_EXEC_PREFIX, 'platbase':_EXEC_PREFIX, 'projectbase':_PROJECT_BASE, # sys.abiflags may not be defined on all platforms. 'abiflags':sys.abiflags except AttributeError: '' } Oscar
On Thu, Feb 20, 2014 at 2:38 AM, Oscar Benjamin
A search through cpython didn't turn up much. There are only two Subscript nodes found by this script and only this one seems relevant: http://hg.python.org/cpython/file/9cfb3d1cf0d1/Lib/sysconfig.py#l514
Yep, I have that one already. In fact, that's one of the ones I pulled into the PEP itself, as it's quite a good example. Going a level further and putting everything into the braces is a logical extension of the improvement, imo. Have added a mention thereof to the PEP. ChrisA
Another idea: things[i] or else None if IndexError You can't claim that the exception is in the wrong place in relation to the 'except' keyword, because there is no 'except' keyword! -- Greg
On Thu, Feb 20, 2014 at 1:58 PM, Greg Ewing
Another idea:
things[i] or else None if IndexError
You can't claim that the exception is in the wrong place in relation to the 'except' keyword, because there is no 'except' keyword!
Added to the Alternative Proposals section for completeness, but I don't particularly like this. It uses three keywords which currently all occur in expression contexts; whether the parser can handle it or not, I suspect there'll be a lot of odd cases where a human has trouble reading it. ChrisA
On 02/19/2014 08:58 PM, Greg Ewing wrote:
Another idea:
things[i] or else None if IndexError
You can't claim that the exception is in the wrong place in relation to the 'except' keyword, because there is no 'except' keyword!
By adding a __bool__ attribue to exceptions that always returns False, you can get some of that, but it has the same issues that logic operators have when a normally False item is also valid data. To get around that we would need a differently based logic system where the caught exception(s) are the only False values. Exception logic with False as an exception. e1 and e2 or e3 Translate to this... try: e1 e2 except False: e3 Then with IndexError. (except IndexError: things[i] or None) Cheers, Ron
On Thu, Feb 20, 2014 at 6:52 PM, Ron Adam
On 02/19/2014 08:58 PM, Greg Ewing wrote:
Another idea:
things[i] or else None if IndexError
You can't claim that the exception is in the wrong place in relation to the 'except' keyword, because there is no 'except' keyword!
By adding a __bool__ attribue to exceptions that always returns False, you can get some of that, but it has the same issues that logic operators have when a normally False item is also valid data.
To get around that we would need a differently based logic system where the caught exception(s) are the only False values.
Exception logic with False as an exception.
e1 and e2 or e3
Translate to this...
try: e1 e2 except False: e3
Then with IndexError.
(except IndexError: things[i] or None)
I've read your post a few times and am still a bit lost. How would the above be evaluated? ChrisA
On 02/20/2014 02:36 AM, Chris Angelico wrote:
By adding a __bool__ attribue to exceptions that always returns False, you can get some of that, but it has the same issues that logic operators have when a normally False item is also valid data.
To get around that we would need a differently based logic system where the caught exception(s) are the only False values.
Exception logic with False as an exception.
e1 and e2 or e3
Translate to this...
try: e1 e2 except False: e3
Then with IndexError.
(except IndexError: things[i] or None)
I've read your post a few times and am still a bit lost. How would the above be evaluated?
Yes, I was just showing how the logic would work, not the exact executable code. Here's a more complete examples. It still needs the parser to translate the 'or's and 'and's in the except expression to the correct calls. def _except_or(self, e1, e2): try: _ = e1() except exc: _ = e2() return _ def _except_and(e1, e2): e1() return e2() Then this ... value = (except exc: e1 and e2 or e3) Would translate to: value = _except_or(exc, lambda: _except_and(lambda: e1, lambda: e2), lambda: e3) and this ... value = (except IndexError: things[i] or None) would be: value = _except_or(exc, lambda: things[i], lambda: None) The expression doesn't need to be just too terms, it could be something more complex. (except KeyError: d1[key] or d2[key] or d3[key] or None) Would give the first dictionary lookup that doesn't raise KeyError or None. Because the exception paths and data paths don't overlap, they could be dictionaries containing exception instances and it would still work. Does that help? cheers, Ron
On Fri, Feb 21, 2014 at 12:23 AM, Ron Adam
The expression doesn't need to be just too terms, it could be something more complex.
(except KeyError: d1[key] or d2[key] or d3[key] or None)
Would give the first dictionary lookup that doesn't raise KeyError or None.
Because the exception paths and data paths don't overlap, they could be dictionaries containing exception instances and it would still work.
Okay. I think I follow. The way to spell that in the current proposal is: d1[key] except KeyError: (d2[key] except KeyError: (d3[key] except KeyError: None)) which is rather more verbose. On the other hand, the syntax you have requires magic around the 'or' keyword. ChrisA
2014-02-20 15:31 GMT+02:00 Chris Angelico
On Fri, Feb 21, 2014 at 12:23 AM, Ron Adam
wrote: The expression doesn't need to be just too terms, it could be something
more
complex.
(except KeyError: d1[key] or d2[key] or d3[key] or None)
Would give the first dictionary lookup that doesn't raise KeyError or None.
Because the exception paths and data paths don't overlap, they could be dictionaries containing exception instances and it would still work.
Okay. I think I follow. The way to spell that in the current proposal is:
d1[key] except KeyError: (d2[key] except KeyError: (d3[key] except KeyError: None))
which is rather more verbose. On the other hand, the syntax you have requires magic around the 'or' keyword.
Perhaps it should be a colon-seperated list: (except KeyError: d1[key] : d2[key] : d3[key] : None) Or maybe a semicolon. (except KeyError: d1[key] ; d2[key] ; d3[key] ; None) --- Elazar
On 02/20/2014 07:41 AM, אלעזר wrote:
2014-02-20 15:31 GMT+02:00 Chris Angelico
mailto:rosuav@gmail.com>: On Fri, Feb 21, 2014 at 12:23 AM, Ron Adam
mailto:ron3200@gmail.com> wrote: The expression doesn't need to be just too terms, it could be something more complex.
(except KeyError: d1[key] or d2[key] or d3[key] or None)
Would give the first dictionary lookup that doesn't raise KeyError or None.
Because the exception paths and data paths don't overlap, they could be dictionaries containing exception instances and it would still work.
Okay. I think I follow. The way to spell that in the current proposal is:
d1[key] except KeyError: (d2[key] except KeyError: (d3[key] except KeyError: None))
which is rather more verbose. On the other hand, the syntax you have requires magic around the 'or' keyword.
Perhaps it should be a colon-seperated list:
(except KeyError: d1[key] : d2[key] : d3[key] : None)
Or maybe a semicolon.
(except KeyError: d1[key] ; d2[key] ; d3[key] ; None)
The semicolon corresponds to "and statement" in it's normal use and has no return value. How about re-using the binary operators... (except KeyError try d1[key] | d2[key] | d3[key] | None) It's not uncommon for symbols to mean different things in different places. %, +, * There are probably others I'm not thinking of at the moment. I really like this actually and it makes the syntax even more concise. :-) Cheers, Ron
On Fri, Feb 21, 2014 at 2:26 AM, Ron Adam
How about re-using the binary operators...
(except KeyError try d1[key] | d2[key] | d3[key] | None)
It's not uncommon for symbols to mean different things in different places.
%, +, *
There are probably others I'm not thinking of at the moment.
I really like this actually and it makes the syntax even more concise. :-)
That works really nicely when you can control the types returned. I've written stuff that abuses magic methods (one of the most fun was producing something that actually stored an indeterminate value in a variable!), but it depends on writing the classes yourself. Can't be used in the general sense. But if you _can_ control it, all you need is to do this: value = d1[key] | d2[key] | d3[key] | None and then define d1[key] to return a special object which, when or'd with something else, tries to look itself up, and if it fails, returns the second object. Actually, here's an even cleaner way. Let's suppose those are dictionary-like objects that you fully control. Just do this: value = (d1 | d2 | d3)[key] and have the "cache | cache" return a helper object that will try one and then the other. Could be beautifully clean... but isn't a replacement for except-expressions. ChrisA
On 02/20/2014 10:17 AM, Chris Angelico wrote:
On Fri, Feb 21, 2014 at 2:26 AM, Ron Adam
wrote: How about re-using the binary operators...
(except KeyError try d1[key] | d2[key] | d3[key] | None)
It's not uncommon for symbols to mean different things in different places.
%, +, *
There are probably others I'm not thinking of at the moment.
I really like this actually and it makes the syntax even more concise.:-)
That works really nicely when you can control the types returned. I've written stuff that abuses magic methods (one of the most fun was producing something that actually stored an indeterminate value in a variable!), but it depends on writing the classes yourself. Can't be used in the general sense.
But if you_can_ control it, all yit was so long agoou need is to do this:
value = d1[key] | d2[key] | d3[key] | None
It needs the syntax part to read correctly, otherwise you would think it's a binary or. And you would still have the issue of early evaluation, so it still needs some special help. The syntax helps to make the meaning clear. value = (except IndexError try items[n] | None) I think with highlighting, this would read very nice and the meaning be unambiguous after learning the use of the binary operators in this context. I don't think that would take long as they aren't frequently used symbols.
and then define d1[key] to return a special object which, when or'd with something else, tries to look itself up, and if it fails, returns the second object. Actually, here's an even cleaner way. Let's suppose those are dictionary-like objects that you fully control. Just do this:
value = (d1 | d2 | d3)[key]
and have the "cache | cache" return a helper object that will try one and then the other. Could be beautifully clean... but isn't a replacement for except-expressions.
I've done something similar with magic methods to implement formula evaluation. With each formula object returning a simplified formula object. (I can't remember the details it was so long ago, but it was a hack in any case.) Just looked at the int and byte types. Are there magic methods for the binary operators? I don't see them, or I'm overlooking them. There are byte codes, BINARY_OR, etc... those wouldn't be used in this case. I don't think this would be hard to implement in byte code. The "&" operation is just executing the two instructions in sequence. The "|", is a standard exception block. The "!" is a raise exception after the expression. The rest is just were to put those in relation to each other, which is handled by the AST. Nothing very special or difficult to implement. Getting the syntax right is the only tricky part, and that may only be because I don't have much experience doing that. Cheers, Ron
On 02/20/2014 11:31 AM, Ron Adam wrote:
I don't think this would be hard to implement in byte code. The "&" operation is just executing the two instructions in sequence. The "|", is a standard exception block. The "!" is a raise exception after the expression. The rest is just were to put those in relation to each other, which is handled by the AST. Nothing very special or difficult to implement. Getting the syntax right is the only tricky part, and that may only be because I don't have much experience doing that.
The '!' may be a bit more than that if it's also suppresses an exception... (except exc try ! e1 & e2) # same as '|' ? Maybe it's not needed. Cheers, Ron
On 02/20/2014 07:31 AM, Chris Angelico wrote:
On Fri, Feb 21, 2014 at 12:23 AM, Ron Adam
wrote: The expression doesn't need to be just too terms, it could be something more complex.
(except KeyError: d1[key] or d2[key] or d3[key] or None)
Would give the first dictionary lookup that doesn't raise KeyError or None.
Because the exception paths and data paths don't overlap, they could be dictionaries containing exception instances and it would still work. Okay. I think I follow. The way to spell that in the current proposal is:
d1[key] except KeyError: (d2[key] except KeyError: (d3[key] except KeyError: None))
which is rather more verbose. On the other hand, the syntax you have requires magic around the 'or' keyword.
ChrisA
Correct. And it would need to be able to differentiate the normal use of 'or' and the except 'or'. Probably with braces in some way. Ron
Chris Angelico wrote:
I don't want to narrow this down to always having to catch everything.
Agreed, that would be unacceptably bad. What *might* be useful is to allow the exception type to be omitted, and have it default to LookupError. Then the most common anticipated use cases would be very concise: things[i] except: default_value -- Greg
On Wed, Feb 19, 2014 at 8:38 AM, Greg Ewing
What *might* be useful is to allow the exception type to be omitted, and have it default to LookupError. Then the most common anticipated use cases would be very concise:
things[i] except: default_value
While that does seem very tempting, I'm strongly against having a dramatic-yet-likely-unnoticed difference between these two: _ = things[i] except: default_value and try: _ = things[i] except: _ = default_value By your suggestion, the first one is equivalent to: _ = things[i] except LookupError: default_value But by current rules of Python, the second is equivalent to: _ = things[i] except BaseException: default_value and that's really REALLY bad to do unexpectedly. Suppose the lookup into things[i] takes a long time (maybe the system's low on memory and has to page stuff back in), and the user hits Ctrl-C while it's doing it. Catching BaseException means getting back the default_value there; catching LookupError means having KeyboardInterrupt propagate upward. Same goes for typoing 'things', or any other sort of error. I want to be able to explain the exception-expression in terms of a similarly-spelled exception-statement, which means that every piece of common syntax should have parallel semantics - just as lambda and def do. That means having a bare except either cause an error, or do the same thing a bare except does in the statement form. ChrisA
On 19 Feb 2014 07:48, "Chris Angelico"
On Wed, Feb 19, 2014 at 8:38 AM, Greg Ewing
wrote:
What *might* be useful is to allow the exception type to be omitted, and have it default to LookupError. Then the most common anticipated use cases would be very concise:
things[i] except: default_value
While that does seem very tempting, I'm strongly against having a dramatic-yet-likely-unnoticed difference between these two:
_ = things[i] except: default_value
and
try: _ = things[i] except: _ = default_value
By your suggestion, the first one is equivalent to:
_ = things[i] except LookupError: default_value
But by current rules of Python, the second is equivalent to:
_ = things[i] except BaseException: default_value
and that's really REALLY bad to do unexpectedly. Suppose the lookup into things[i] takes a long time (maybe the system's low on memory and has to page stuff back in), and the user hits Ctrl-C while it's doing it. Catching BaseException means getting back the default_value there; catching LookupError means having KeyboardInterrupt propagate upward. Same goes for typoing 'things', or any other sort of error. I want to be able to explain the exception-expression in terms of a similarly-spelled exception-statement, which means that every piece of common syntax should have parallel semantics - just as lambda and def do. That means having a bare except either cause an error, or do the same thing a bare except does in the statement form.
+1000 from me for disallowing bare except in the expression form - suppressing SystemExit and KeyError is bad, and if an explicit exception is required, people are more likely to spell "catch everything" as "except Exception:" rather than "except BaseException:" . Cheers, Nick.
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/
PEP: XXX Title: Exception-catching expressions Version: $Revision$ Last-Modified: $Date$ Author: Chris Angelico
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. Oops, I almost missed this (and was going to point out the omission, sorry!). Yes. So in 1 / x except ZeroDivisionError: 42 an error will be trapped evaluating "1/x" (not just "x") because "/" has higher precedence than "except", as we would all expect.
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. I don't see (though I could be missing something) that "finally" makes any sense inside an expression. "finally" introduces an *action* to be
On 16/02/2014 04:04, Chris Angelico wrote: performed regardless of whether a preceding operation raised an error. Here we are evaluating an *expression*, and to tack on "I want to perform this action" on the end of an expression seems weird (and better done by the existing try ... finally mechanism). It did occur to me briefly that it might be used to provide a final default: x = eval(UserExpression) except NameError: "You forgot to define something" except ZeroDivisionError: "Division by zero" finally: "Some error or other" but this would be pretty well equivalent to using a bare "except:" clause, or "except BaseException". Oh yes, by the way, I think that a bare "except:" should be legal (for consistency), albeit discouraged.
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.
I would argue against comma separators being allowed: 1) They add extra grit to the syntax. 2) When the parser sees a comma, it doesn't know if this is terminating the except clause, or part of a tuple default value: value = expr except Exception1: x,y, except Exception2: z (The ambiguity is resolved when it sees another "except". The intervening comma just adds confusion and makes the parser's job *harder*.) And for what it's worth, if you did want a 1-element tuple as your default value you might write the ugly value = expr except Exception1: x,, except Exception2: z # Yuk! Please put "x," inside parentheses. 3) With "no commas", the subtly different semantics you mention would be distinguished transparently by value = expr except Exception1: default1 except Exception2: default2 value = (expr except Exception1: default1) except Exception2: default2 In the first form, Exception2 will not be caught if it is raised evaluating default1. In the second it will be. Rob Cliffe
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 _______________________________________________ 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/6597 - Release Date: 02/16/14
On Mon, Feb 17, 2014 at 5:24 PM, Rob Cliffe
So in 1 / x except ZeroDivisionError: 42 an error will be trapped evaluating "1/x" (not just "x") because "/" has higher precedence than "except", as we would all expect.
Yes, definitely. Operator precedence can (and should) be used to make this do exactly what everyone expects. Though there's a proposal on the board to demand parens: (1 / x except ZeroDivisionError: 42) which would settle the deal.
I don't see (though I could be missing something) that "finally" makes any sense inside an expression. "finally" introduces an action to be performed regardless of whether a preceding operation raised an error. Here we are evaluating an expression, and to tack on "I want to perform this action" on the end of an expression seems weird (and better done by the existing try ... finally mechanism). It did occur to me briefly that it might be used to provide a final default: x = eval(UserExpression) except NameError: "You forgot to define something" except ZeroDivisionError: "Division by zero" finally: "Some error or other" but this would be pretty well equivalent to using a bare "except:" clause, or "except BaseException".
Agreed, finally makes no use. That section has been reworded, but is worth keeping.
Oh yes, by the way, I think that a bare "except:" should be legal (for consistency), albeit discouraged.
Agreed. Never even thought about it, but yes, that's legal. No reason to forbid it. Added a sentence to the PEP to say so.
I would argue against comma separators being allowed: 1) They add extra grit to the syntax. 2) When the parser sees a comma, it doesn't know if this is terminating the except clause, or part of a tuple default value: value = expr except Exception1: x,y, except Exception2: z (The ambiguity is resolved when it sees another "except". The intervening comma just adds confusion and makes the parser's job harder.) And for what it's worth, if you did want a 1-element tuple as your default value you might write the ugly value = expr except Exception1: x,, except Exception2: z # Yuk! Please put "x," inside parentheses. 3) With "no commas", the subtly different semantics you mention would be distinguished transparently by
value = expr except Exception1: default1 except Exception2: default2 value = (expr except Exception1: default1) except Exception2: default2 In the first form, Exception2 will not be caught if it is raised evaluating default1. In the second it will be.
I'm not convinced either way about the commas, at the moment. At some point, I'm probably going to have to come up with a bunch of actual examples (real-world or synthesized) and then show how each would be written as a statement, and then as an expression, and show the latter with the full cross-multiplication of proposals: commas or not, parens or not, etc, etc, etc. In any case, I'm pretty happy with the level of interest this is generating. A number of people have strong opinions on the matter. That's a good sign! I'm happy to re-post the current version of the PEP any time people like; or, if it makes sense, I could shove the repository onto github so people can see the changes live. At some point, of course, the authoritative text will be in the official PEP repository, but until then, I'm open to suggestions. ChrisA
On Mon, Feb 17, 2014 at 5:40 PM, Chris Angelico
I'm happy to re-post the current version of the PEP any time people like; or, if it makes sense, I could shove the repository onto github so people can see the changes live. At some point, of course, the authoritative text will be in the official PEP repository, but until then, I'm open to suggestions.
Since github's easy for me to do, I've posted it there. Here's the current version of the PEP; this URL will point to the latest, until it gets accepted into the main PEPs repo. https://raw2.github.com/Rosuav/ExceptExpr/master/pep.txt Let me know if I've missed anything. I've been reading every message that goes through, but sometimes I don't properly reflect comments into the draft PEP. ChrisA
On 16.02.2014 05:04, Chris Angelico wrote:
PEP: XXX Title: Exception-catching expressions Version: $Revision$ Last-Modified: $Date$ Author: Chris Angelico
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 writing up this PEP.
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 colon in there breaks the basic Python concept of having colons end headers which start a new block of statements: http://docs.python.org/reference/compound_stmts.html I think it would be better to have the PEP focus on a solution that doesn't introduce more lambda like syntax to the language :-) Please also add examples where the expression notation is used as part of a larger expression, e.g. value = lst[2] except IndexError: 0, 1 value = lst[2] except IndexError: 0 if x else -1 value = lst[2] except IndexError: 0 if lst[1] except IndexError: False else -1 value = lst[2] except IndexError: 0 + lst[0] d = {'a': lst[2] except IndexError: 0, 'b': lst[1] except IndexError: 0 if lst[0] else 1, 'c': lst[0], } Or code like this: try: x = lst[0] except IndexError: x = lst[2] except Exception: lst[1] l.sort(key = lambda x: x[1] except IndexError: x[0]) IMO, the colon in there makes the code less readable than the variants which try to reuse a keyword. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Feb 17 2014)
Python Projects, Consulting and Support ... http://www.egenix.com/ mxODBC.Zope/Plone.Database.Adapter ... http://zope.egenix.com/ mxODBC, mxDateTime, mxTextTools ... http://python.egenix.com/
2014-02-12: Released mxODBC.Connect 2.0.4 ... http://egenix.com/go53 ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/
On 17 February 2014 10:00, M.-A. Lemburg
Please also add examples where the expression notation is used as part of a larger expression
+1. When I see isolated exception-catching expressions as examples, I think in terms of assigning them to something, and that is often better done with a try/except statement. So it doesn't show the benefits of the expression form. Paul
On 17/02/2014 10:00, M.-A. Lemburg wrote:
On 16.02.2014 05:04, Chris Angelico wrote: The colon in there breaks the basic Python concept of having colons end headers which start a new block of statements:
http://docs.python.org/reference/compound_stmts.html
I think it would be better to have the PEP focus on a solution that doesn't introduce more lambda like syntax to the language :-)
Please also add examples where the expression notation is used as part of a larger expression, e.g.
value = lst[2] except IndexError: 0, 1 value = lst[2] except IndexError: 0 if x else -1 value = lst[2] except IndexError: 0 if lst[1] except IndexError: False else -1 value = lst[2] except IndexError: 0 + lst[0] d = {'a': lst[2] except IndexError: 0, 'b': lst[1] except IndexError: 0 if lst[0] else 1, 'c': lst[0], }
Just wanted to point out the ambiguity in some of these, at least to a human reader. If I am not mistaken, the proposed precedence for "except" (higher than "lambda", lower than everything else including "if - else") means that: The first one means value = lst[2] except IndexError: (0,1) not value = (lst[2] except IndexError: 0), 1 The second means value = lst[2] except IndexError: (0 if x else -1) not value = (lst[2] except IndexError: 0) if x else -1 The third means value = lst[2] except IndexError: (0 if (lst[1] except IndexError: False) else -1) rather than anything else (this is getting a bit mind-boggling). The fourth means value = lst[2] except IndexError: (0 + lst[0]) rather than value = (lst[2] except IndexError: 0) + lst[0] I wonder if the actual meaning coincides in all cases with your intention when you wrote them? No offence intended, MAL, I'm not trying to slag you off, just pointing out some of the issues that arise. :-) And wondering if there is a case for "except" having a higher precedence than "if - else". I don't have an opinion yet, I'm just thinking aloud. The last one is I believe unambiguous except for the value associated with the key 'b', but to my mind is another reason for not allowing commas in the proposed "except expression" syntax - confusion with commas as separators between dictionary literals (sorry I keep plugging this!). There is already ISTM a clash between comma as separator between dictionary items and between tuple elements:
{ 'a' : (1,3), 'b' : 2 } { 'a': (1, 3), 'b': 2 } { 'a' : 1,3, 'b' : 2 } # Python 2.7.3 SyntaxError: invalid syntax
so I think it would be a mistake to overload the comma further. Best wishes, Rob Cliffe
On 17.02.2014 15:33, Rob Cliffe wrote:
On 17/02/2014 10:00, M.-A. Lemburg wrote:
On 16.02.2014 05:04, Chris Angelico wrote: The colon in there breaks the basic Python concept of having colons end headers which start a new block of statements:
http://docs.python.org/reference/compound_stmts.html
I think it would be better to have the PEP focus on a solution that doesn't introduce more lambda like syntax to the language :-)
Please also add examples where the expression notation is used as part of a larger expression, e.g.
value = lst[2] except IndexError: 0, 1 value = lst[2] except IndexError: 0 if x else -1 value = lst[2] except IndexError: 0 if lst[1] except IndexError: False else -1 value = lst[2] except IndexError: 0 + lst[0] d = {'a': lst[2] except IndexError: 0, 'b': lst[1] except IndexError: 0 if lst[0] else 1, 'c': lst[0], }
Just wanted to point out the ambiguity in some of these, at least to a human reader.
This ambiguity is the reason why I pointed at these examples :-)
If I am not mistaken, the proposed precedence for "except" (higher than "lambda", lower than everything else including "if - else") means that:
The first one means value = lst[2] except IndexError: (0,1) not value = (lst[2] except IndexError: 0), 1
The second is what Python would do. You can use if-else for testing this:
1 if 2 else 3, 4 (1, 4)
The second means value = lst[2] except IndexError: (0 if x else -1) not value = (lst[2] except IndexError: 0) if x else -1
Yes.
The third means value = lst[2] except IndexError: (0 if (lst[1] except IndexError: False) else -1) rather than anything else (this is getting a bit mind-boggling).
Yes; but I'm not entirely sure if the Python parser could even handle this case. It also fails on this example:
1 if 2 if 3 else 4 else 5 File "<stdin>", line 1 1 if 2 if 3 else 4 else 5 ^ SyntaxError: invalid syntax
The fourth means value = lst[2] except IndexError: (0 + lst[0]) rather than value = (lst[2] except IndexError: 0) + lst[0]
Yes.
I wonder if the actual meaning coincides in all cases with your intention when you wrote them? No offence intended, MAL, I'm not trying to slag you off, just pointing out some of the issues that arise. :-)
Looking at such issues was the intention of posting the list :-)
And wondering if there is a case for "except" having a higher precedence than "if - else". I don't have an opinion yet, I'm just thinking aloud.
The last one is I believe unambiguous except for the value associated with the key 'b', but to my mind is another reason for not allowing commas in the proposed "except expression" syntax - confusion with commas as separators between dictionary literals (sorry I keep plugging this!). There is already ISTM a clash between comma as separator between dictionary items and between tuple elements:
{ 'a' : (1,3), 'b' : 2 } { 'a': (1, 3), 'b': 2 } { 'a' : 1,3, 'b' : 2 } # Python 2.7.3 SyntaxError: invalid syntax
so I think it would be a mistake to overload the comma further.
Commas are indeed too overloaded already. Guess what these evaluate to :-) x = lambda y: y, 2 x = 2, x = 1, 2, x = {1:2,} -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Feb 17 2014)
Python Projects, Consulting and Support ... http://www.egenix.com/ mxODBC.Zope/Plone.Database.Adapter ... http://zope.egenix.com/ mxODBC, mxDateTime, mxTextTools ... http://python.egenix.com/
2014-02-12: Released mxODBC.Connect 2.0.4 ... http://egenix.com/go53 ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/
On Tue, Feb 18, 2014 at 2:00 AM, M.-A. Lemburg
Guess what these evaluate to :-)
x = lambda y: y, 2 x = 2, x = 1, 2, x = {1:2,}
The first one is the only one that might be at all in question. The others are straight-forward: a one-element tuple, a two-element tuple, and a one-element dict. The last two are simply making use of the fact that trailing commas are permitted, which is *extremely* handy in avoiding code churn. ChrisA
On Tue, Feb 18, 2014 at 2:00 AM, M.-A. Lemburg
x = 1, 2, x = {1:2,}
Actually, a more confusing pair than those is this: x = {1,2,} x = {1:2,} In terms of the "grit on the screen syntax" problem, this one is at some risk. My currently-preferred proposal for exceptions makes good use of the 'except' keyword, so it should be fairly clear what's going on. ChrisA
On Feb 17, 2014, at 7:08, Chris Angelico
On Tue, Feb 18, 2014 at 2:00 AM, M.-A. Lemburg
wrote: x = 1, 2, x = {1:2,}
Actually, a more confusing pair than those is this:
x = {1,2,} x = {1:2,}
In terms of the "grit on the screen syntax" problem, this one is at some risk. My currently-preferred proposal for exceptions makes good use of the 'except' keyword, so it should be fairly clear what's going on.
That's just because set displays are somewhat degenerate. With 0 elements they are impossible to distinguish from dict displays are therefore illegal; with 1 or 2 elements they can be hard for humans over 30 to distinguish from dict displays (especially without PEP 8-style proper spacing, as in your examples) and are therefore a bit risky; it's just a lesser version of the same problem.
On Mon, Feb 17, 2014 at 04:00:28PM +0100, M.-A. Lemburg wrote:
Commas are indeed too overloaded already.
Guess what these evaluate to :-)
x = lambda y: y, 2 x = 2, x = 1, 2, x = {1:2,}
With the exception of the first one, where I'm not only 90% confident, I don't think there is any difficulty with any of those. The first makes x a tuple containing (function, 2). The second is a tuple of one element, (2,). (Remember that, with the exception of the empty tuple, tuples are created by commas, not round brackets.) The third is a tuple of two elements, (1, 2). The last is a dict with one key:item pair {1:2}. The only one I needed to check at the interactive interpreter was the one with the lambda. -- Steven
On Mon, Feb 17, 2014 at 4:07 PM, Steven D'Aprano
Guess what these evaluate to :-)
x = lambda y: y, 2 x = 2, x = 1, 2, x = {1:2,}
With the exception of the first one, where I'm not only 90% confident, I don't think there is any difficulty with any of those.
Same here.
The first makes x a tuple containing (function, 2).
Funny: less than an hour ago, I paused when writing a lambda to return a tuple. I conservatively put parentheses around the tuple and did not bother to check if they are required. Now I know that they are.
Alexander Belopolsky wrote:
Funny: less than an hour ago, I paused when writing a lambda to return a tuple. I conservatively put parentheses around the tuple and did not bother to check if they are required. Now I know that they are.
It's even more confusing when you consider that you can write lambda x, y: x + y, 2 and the first comma doesn't break up the lambda, but the second one does! With oddities like this already in the language, I don't think we need to worry too much about the corner cases. People are always free to insert parens to make things clearer. E.g. I would probably write the above as (lambda (x, y): x + y), 2 -- Greg
On Tue, Feb 18, 2014 at 06:19:28PM +1300, Greg Ewing wrote:
Alexander Belopolsky wrote:
Funny: less than an hour ago, I paused when writing a lambda to return a tuple. I conservatively put parentheses around the tuple and did not bother to check if they are required. Now I know that they are.
It's even more confusing when you consider that you can write
lambda x, y: x + y, 2
and the first comma doesn't break up the lambda, but the second one does!
It shouldn't be confusing. It would be confusing if the expression was broken up in this way: (lambda x), (y: x+y), 2 since it is semantically meaningless. The parser here does the right thing, parsing an expression in a way that is semantically sound.
With oddities like this already in the language, I don't think we need to worry too much about the corner cases.
But the corner cases could include bugs in the parser, at least theoretically, as well as bugs in people's code. And that is definitely not theoretical. For example, the old form of try...except blocks was a terrible bug magnet: try: stuff() except ValueError, error: print error.arguments People would write: except ValueError, TypeError: and the caught ValueError would be bound to the name TypeError, instead of catching TypeError.
People are always free to insert parens to make things clearer. E.g. I would probably write the above as
(lambda (x, y): x + y), 2
Well, you could try to, but it won't work in Python 3. That's a SyntaxError. -- Steven
On Wed, Feb 19, 2014 at 8:41 AM, Steven D'Aprano
People are always free to insert parens to make things clearer. E.g. I would probably write the above as
(lambda (x, y): x + y), 2
Well, you could try to, but it won't work in Python 3. That's a SyntaxError.
And it's semantically different in Py2 - it takes a two-element tuple, rather than two args. ChrisA
M.-A. Lemburg wrote:
The colon in there breaks the basic Python concept of having colons end headers which start a new block of statements:
That section is talking about statements. The construct we're discussing is an expression, so it wouldn't be bound by the same rules. And there are at least 3 existing uses of colons in expressions, so ending headers is not the *only* use of colons. -- Greg
On 2/15/2014, 11:04 PM, Chris Angelico wrote:
[snip] 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
How about adding a new keyword? If we add new 'raises' keyword, then we can have the following new syntax: # set 'value' to 42 if len(a) < 10 # or else, return a[10] value = 42 if a[10] raises IndexError # set 'value' to 42 if len(a) < 10 # or else, return 'spam' value = 42 if a[10] raises IndexError else 'spam' # same as above but if a[10] raises anything value = 42 if a[10] raises # another example foo(None if dct['key'] raises) This syntax doesn't provide: - finally statement replacement; - handling multiple exceptions; - rerising exception; which I think is a good thing. No need to complicate this syntax. Pros: - easy to read (like English or pseudocode) - looks like existing 'expr if expr else expr' syntax Cons: - adds a new keyword Yury
On 02/19/2014 04:22 PM, Yury Selivanov wrote:
If we add new 'raises' keyword, then we can have the following new syntax:
# set 'value' to 42 if len(a) < 10 # or else, return a[10] value = 42 if a[10] raises IndexError
# set 'value' to 42 if len(a) < 10 # or else, return 'spam' value = 42 if a[10] raises IndexError else 'spam'
# same as above but if a[10] raises anything value = 42 if a[10] raises
# another example foo(None if dct['key'] raises)
The big problems I see with this idea are that: - the emphasis is placed on the unusual rather than the normal result - it's not clear what the normal result should be For the second point, consider: value = 42 if a[10] raises There is nothing there to indicate that the value of a[10] is what should be used if no exception is raised. -1 -- ~Ethan~
On Thu, Feb 20, 2014 at 11:22 AM, Yury Selivanov
If we add new 'raises' keyword, then we can have the following new syntax:
Cost of adding a keyword is based on its usage. Searching the standard library for 'raises' comes up blank; same goes for 'then', though both 'use' and 'when' do come up (once and twelve times, respectively). I think it reasonably unlikely that 'raises' will be a problem. However, it's very similar to an existing keyword. The English form sounds a little archaic, but plausible: "Do this if spam raise something".
# set 'value' to 42 if len(a) < 10 # or else, return a[10] value = 42 if a[10] raises IndexError
(Minor point: That should be 42 if len(a) <= 10.)
# set 'value' to 42 if len(a) < 10 # or else, return 'spam' value = 42 if a[10] raises IndexError else 'spam'
Have you a use-case for this? It completely ignores the original expression value.
# same as above but if a[10] raises anything value = 42 if a[10] raises
I'm giving this an immediate common-law rejection, as the bare-except sub-proposal has already been rejected.
# another example foo(None if dct['key'] raises)
And what if it doesn't? Reads nicely for the exceptional case. Presumably it's the value of dct['key'] if it doesn't raise, but that's not obvious from how it reads.
This syntax doesn't provide: - finally statement replacement; - handling multiple exceptions; - rerising exception; which I think is a good thing. No need to complicate this syntax.
I'm fine with those absences.
Pros: - easy to read (like English or pseudocode) - looks like existing 'expr if expr else expr' syntax
Cons: - adds a new keyword
That's not a killer, by any means. My main objection here is that it reads backwards. This is an issue with the "if/else" operator in Python, too. The actual evaluation order has to be: 1) Base expression 2) Exception list 3) Default expression I could accept switching 1 and 2, since the exception list will normally be constant anyway (some of the functional forms effectively do this, by having lambdas for the other two but not for the exception_list), but quite a few good use-cases depend on the default expression being calculated lazily. So if the base expression is evaluated before the default expression, it makes better sense to put base to the left of default. That way, the expression reads in the order that it should:
[print("1"), print("2")][print("3") or 0] 1 2 3
print("2") if not print("1") else print("n/a") 1 2
There's no changing if/else, but I'm still inclined to support proposals that put the expressions in the "correct order". That's not a clincher on its own, but I want to see a compelling use-case that justifies the out-of-order layout. ChrisA
On 2/19/2014, 7:56 PM, Chris Angelico wrote:
If we add new 'raises' keyword, then we can have the following new syntax: Cost of adding a keyword is based on its usage. Searching the standard
On Thu, Feb 20, 2014 at 11:22 AM, Yury Selivanov
wrote: library for 'raises' comes up blank; same goes for 'then', though both 'use' and 'when' do come up (once and twelve times, respectively). I think it reasonably unlikely that 'raises' will be a problem. However, it's very similar to an existing keyword. The English form sounds a little archaic, but plausible: "Do this if spam raise something". # set 'value' to 42 if len(a) < 10 # or else, return a[10] value = 42 if a[10] raises IndexError (Minor point: That should be 42 if len(a) <= 10.)
# set 'value' to 42 if len(a) < 10 # or else, return 'spam' value = 42 if a[10] raises IndexError else 'spam' Have you a use-case for this? It completely ignores the original expression value.
Something like result = 'failed' if command() raises else 'ok'
# same as above but if a[10] raises anything value = 42 if a[10] raises I'm giving this an immediate common-law rejection, as the bare-except sub-proposal has already been rejected.
Since it is a new keyword, we can document that it can intercept only Exceptions, not BaseExceptions.
# another example foo(None if dct['key'] raises) And what if it doesn't? Reads nicely for the exceptional case. Presumably it's the value of dct['key'] if it doesn't raise, but that's not obvious from how it reads.
Without this you would need to write: foo(None if dct['key'] raises else dct[key]) Which is code duplication and a performance impact, since we'll have to evaluate expression twice.
This syntax doesn't provide: - finally statement replacement; - handling multiple exceptions; - rerising exception; which I think is a good thing. No need to complicate this syntax. I'm fine with those absences.
Pros: - easy to read (like English or pseudocode) - looks like existing 'expr if expr else expr' syntax
Cons: - adds a new keyword That's not a killer, by any means.
My main objection here is that it reads backwards. Yes, that's understandable.
Although, as you yourself noted, Python has 'expr if expr else expr' already. Yes, it is a bit awkward, but lots of people (including me) find it very readable and easy to understand. Same with my proposal. If follows the already established syntax and reads nicely. Could you please add to the PEP (in other proposals section?) Yury
On Thu, Feb 20, 2014 at 12:11 PM, Yury Selivanov
On 2/19/2014, 7:56 PM, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:22 AM, Yury Selivanov
# set 'value' to 42 if len(a) < 10 # or else, return 'spam' value = 42 if a[10] raises IndexError else 'spam'
Have you a use-case for this? It completely ignores the original expression value.
Something like result = 'failed' if command() raises else 'ok'
Yep, I can see the theory of it. What I mean is, do you have actual production code that looks like this? Concrete examples are usually easier to argue based on. How would you currently spell it? try: command() result = 'ok' except SomeException: result = 'failed' Do you have code like that? Part of the question here is that, if the thing you're testing is going to have its return value ignored anyway, does it need to be an expression? I mean, why not do this: result = 'div by zero' if x += 1/y raises else 'no error' which would be some serious pain for the parser. But having an expression that deliberately ignores one of its subexpressions is a bit weird. We don't usually write this, unless we're going for obfuscation: value = (l.append("x"), l)[-1] Although I could imagine that a MacroPy trick be done to make that more useful, eg for method chaining: value = (l.append("x"), l)[-1].append("y") But for exception handling where you don't care about the value of the expression, there's already an obvious way to spell it, and that's the statement form. So I'm looking to see a compelling use-case from production code that shows the benefit of this notation.
# same as above but if a[10] raises anything value = 42 if a[10] raises
I'm giving this an immediate common-law rejection, as the bare-except sub-proposal has already been rejected.
Since it is a new keyword, we can document that it can intercept only Exceptions, not BaseExceptions.
Yes, but overly-broad catching is a big problem even without catching "Exception..BaseException" (if I may use the git notation here - meaning "everything reachable from BaseException but not from Exception"). Earlier in this discussion we proposed having a bare except catch some specific set of "common exceptions". The results of that discussion are in the PEP's rejection section; one of the largest objections doesn't apply here (consistency with the statement form bare except), but the others do.
# another example foo(None if dct['key'] raises)
And what if it doesn't? Reads nicely for the exceptional case. Presumably it's the value of dct['key'] if it doesn't raise, but that's not obvious from how it reads.
Without this you would need to write: foo(None if dct['key'] raises else dct[key])
Which is code duplication and a performance impact, since we'll have to evaluate expression twice.
Right, not to mention a correctness error if the evaluation has side effects. But if you read it in English, there does need to be an "else" clause, otherwise you're left with the "And what if it doesn't?" that I started with. It reads very nicely for the exceptional case, but much less nicely for the normal case. I don't dispute the semantics though.
My main objection here is that it reads backwards.
Yes, that's understandable.
Although, as you yourself noted, Python has 'expr if expr else expr' already. Yes, it is a bit awkward, but lots of people (including me) find it very readable and easy to understand.
Yes, but is that a good thing? The ternary if is justified because it reads well in English, but even so, it does trap people. I am listening to proposals that put the operands in the "wrong order", but the bar is higher - they have to justify that potential confusion.
Same with my proposal. If follows the already established syntax and reads nicely.
Could you please add to the PEP (in other proposals section?)
It's in there. I split it into two parts: firstly, a nearly-equivalent proposal that uses the archaic "raise" (as that's already a keyword), and then a comment underneath about creating a new keyword. ChrisA
On Thu, Feb 20, 2014 at 12:48:33PM +1100, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 12:11 PM, Yury Selivanov
wrote: On 2/19/2014, 7:56 PM, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:22 AM, Yury Selivanov
value = 42 if a[10] raises My main objection here is that it reads backwards.
Yes, that's understandable.
Although, as you yourself noted, Python has 'expr if expr else expr' already. Yes, it is a bit awkward, but lots of people (including me) find it very readable and easy to understand.
Yes, but is that a good thing? The ternary if is justified because it reads well in English, but even so, it does trap people. I am listening to proposals that put the operands in the "wrong order", but the bar is higher - they have to justify that potential confusion.
Moreover the analogy with the if expression is completely bogus: value = <expr on exception> if <expr on success> raises is the *opposite* way round to value = <expr if true> if <cond> else <expr if false> In the if expression, the mainline value comes first, the exceptional value comes last. Yury's proposal has it the other way round, exceptional value first.
On 02/19/2014 05:11 PM, Yury Selivanov wrote:
Same with my proposal. If follows the already established syntax and reads nicely.
I agree it reads nicely, at least in the case of an exception being raised. But it's entirely unclear what the result should be if no exception is raised. -- ~Ethan~
Yury Selivanov writes:
value = 42 if a[10] raises IndexError
"If EXPR raises EXCEPTION" is often interpreted to mean "EXPR may raise EXCEPTION" following the usage in Java and other languages where exceptions are part of function signatures. Maybe it's just me, but this ambiguity would be annoying every time I see this syntax.
# set 'value' to 42 if len(a) < 10 # or else, return 'spam' value = 42 if a[10] raises IndexError else 'spam'
# same as above but if a[10] raises anything value = 42 if a[10] raises
Please, no bare excepts. They're bad enough in the statement form where the colon immediately follows and it's painfully obvious they're bare.
# another example foo(None if dct['key'] raises)
And what's the value if dct['key'] quietly succeeds? I suppose it's dct['key'], but that is not at all what the English interpretation would be! At least in my dialect, the English interpretation would correspond to try: dct['key'] foo() except: foo(None)
16.02.2014 00:46, Chris Angelico wrote:
On Sun, Feb 16, 2014 at 10:17 AM, Greg Ewing
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. [snip]
That's why what a proposed was: things[i] except (IndexError: 42) -- rather than: things[i] (except IndexError: 42). Also, see below...
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) [snip]
In terms of my proposal it would clearly be just: expr except (Exception1: default1) except (Exception2: default2) Of course grouping parens could be added without changing semantics: (expr except (Exception1: default1)) except (Exception2: default2) Also, see below... 15.02.2014 19:11, Steven D'Aprano wrote:
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.
Indeed, though please note that my proposal is a bit different: things[i] except (IndexError: 42) possibly extendable to: # with 'as' clause' some_io() except (OSError as exc: exc.errno) ...and/or: # with multiple exception catches some_io() except (FileNotFoundError: 42, OSError as exc: exc.errno) Before I posted the proposal I did think about the "things[i] (except ..." variant also but I don't like that the opening parenthesis character suggest to a human reader that it is a call... On the other hand, when the parenthesis is *after* the 'except' keyword it is clear for human readers that it is a dedicated syntax having nothing to do with a call. Also, for a multiple-exception-catches-variant I don't like repeating the 'except' keyword for each catch, as well as the ambiguity whether the consecutive 'except...' concerns only the initial expression or also the preceding 'except...'). In my proposal there is no such ambiguity. I also believe that making the parens *obligatory* is a good thing as then: * things are much more clear when several expressions are combined (e.g. except-expression + conditional expression; except-expression + another except-expression, except-expression + generator-expression...); * it is clear that the colon has nothing to do with opening a code block; * it just reads better than without parens. More complex (though probably not very realistic) example: msg = (cache[k] except ( LookupError: backend.read() except ( OSError: 'resource not available')) if check_permission(k) else 'access not allowed' ) except (Exception: 'internal error occurred') Cheers. *j
On Sun, Feb 16, 2014 at 11:01 PM, Jan Kaliszewski
In terms of my proposal it would clearly be just:
expr except (Exception1: default1) except (Exception2: default2)
Of course grouping parens could be added without changing semantics:
(expr except (Exception1: default1)) except (Exception2: default2)
Also, see below...
Actually that _does_ change semantics, but in an extremely subtle way. In both versions, expr raising Exception2 will result in default2; but the first version is effectively this: try: _ = expr except Exception1: _ = default1 except Exception2: _ = default2 value_of_expression = _ If the evaluation of default1 raises Exception2, then this form will propagate that exception up. The second form is actually like this: try: try: _ = expr except Exception1: _ = default1 except Exception2: _ = default2 value_of_expression = _ In this, the evaluation of default1 is inside the outer try block, so if it raises Exception2, the expression result will be default2. It's an extremely subtle difference (since, in most normal cases, the default expressions will be very simple), but it is a difference. Also, the interpreter's forced to construct and process two try/except blocks, for what that's worth. I expect this won't ever be significant in a real-world case, but there is a difference:
timeit.repeat("try: 1/0\nexcept NameError: pass\nexcept ZeroDivisionError: pass") [2.3123623253793433, 2.240881173445228, 2.2435943674405543] timeit.repeat("try:\n try: 1/0\n except NameError: pass\nexcept ZeroDivisionError: pass") [2.9560086547312565, 2.958433264562956, 2.9855417378465674]
That's of the order of five microseconds per iteration, if I'm reading my figures correctly, so that's not exactly significant... but I still see no reason to go to the extra work unnecessarily :)
possibly extendable to:
# with 'as' clause' some_io() except (OSError as exc: exc.errno)
Definitely want to keep the 'as' clause in there, although it does raise some issues of its own as regards name binding. (responding out of order as your code example nicely illustrates your next point)
Before I posted the proposal I did think about the "things[i] (except ..." variant also but I don't like that the opening parenthesis character suggest to a human reader that it is a call... On the other hand, when the parenthesis is *after* the 'except' keyword it is clear for human readers that it is a dedicated syntax having nothing to do with a call.
I don't see it as particularly significant either way. Opening parens and immediately having a keyword like "except" is strongly indicative of something different going on; same goes for having that keyword just _before_ the parens.
...and/or:
# with multiple exception catches some_io() except (FileNotFoundError: 42, OSError as exc: exc.errno)
Also, for a multiple-exception-catches-variant I don't like repeating the 'except' keyword for each catch, as well as the ambiguity whether the consecutive 'except...' concerns only the initial expression or also the preceding 'except...'). In my proposal there is no such ambiguity.
There's an ambiguity in this version, though. After the 'except' keyword, you can have a tuple of exceptions: try: 1/x except (NameError, ZeroDivisionError): print("Can't divide by nothing!") If the previous value is followed by a comma and the next thing could be a tuple, the parsing is going to get a bit hairy. Either the previous value or the exception_list could be a tuple. I'd rather repeat the word 'except'.
I also believe that making the parens *obligatory* is a good thing
I'm happy with them being optional in cases where it's unambiguous, but I'd be okay with mandating them to disambiguate. After all, if the language says they're optional, style guides have the option of mandating; but if the language mandates, the freedom is gone.
More complex (though probably not very realistic) example:
msg = (cache[k] except ( LookupError: backend.read() except ( OSError: 'resource not available')) if check_permission(k) else 'access not allowed' ) except (Exception: 'internal error occurred')
Fairly unrealistic, as the backend.read() call won't get put into the cache (and you can't put the final msg into the cache, as 'resource not available' sounds temporary and thus non-cached), not to mention that swallowing Exception to just return a constant string seems like a really REALLY bad idea! :) But this highlights another big issue: How should a complex except expression be laid out? With the statement form, it's pretty clear: first you do the line(s) that you're trying, then you have your except clauses, no more than one on any given line, and then you have your else, and your finally, if you have either. Everything's on separate lines. What about here? If it all fits on one line, great! But it often won't (the name "ZeroDivisionError" is over a fifth of your typical line width, all on its own), and then where do you break it? Not technically part of the proposal, but it's going to be an issue. ChrisA
16.02.2014 14:01, Chris Angelico wrote:
On Sun, Feb 16, 2014 at 11:01 PM, Jan Kaliszewski
wrote: In terms of my proposal it would clearly be just:
expr except (Exception1: default1) except (Exception2: default2)
Of course grouping parens could be added without changing semantics:
(expr except (Exception1: default1)) except (Exception2: default2)
Also, see below...
Actually that _does_ change semantics, but in an extremely subtle way.
As log as we are talking about my proposal -- it does not. The
proposed
syntax is roughly:
In both versions, expr raising Exception2 will result in default2; but the first version is effectively this:
try: _ = expr except Exception1: _ = default1 except Exception2: _ = default2 value_of_expression = _
If the evaluation of default1 raises Exception2, then this form will propagate that exception up. The second form is actually like this:
try: try: _ = expr except Exception1: _ = default1 except Exception2: _ = default2 value_of_expression = _
No, both expression forms I mentioned are equivalent to the *latter* (the nested one) of the above snippets. Equivalent to the *former* (the "flat" one) of the above snippets would be just the expression: expr except (Exception1: default1, Exception2: default2) [snip]
Before I posted the proposal I did think about the "things[i] (except ..." variant also but I don't like that the opening parenthesis character suggest to a human reader that it is a call... On the other hand, when the parenthesis is *after* the 'except' keyword it is clear for human readers that it is a dedicated syntax having nothing to do with a call.
I don't see it as particularly significant either way. Opening parens and immediately having a keyword like "except" is strongly indicative of something different going on; same goes for having that keyword just _before_ the parens.
Apparently it's a subjective matter as I feel that "expr except (...)" is less misleading for human eyes...
...and/or:
# with multiple exception catches some_io() except (FileNotFoundError: 42, OSError as exc: exc.errno)
Also, for a multiple-exception-catches-variant I don't like repeating the 'except' keyword for each catch, as well as the ambiguity whether the consecutive 'except...' concerns only the initial expression or also the preceding 'except...'). In my proposal there is no such ambiguity.
There's an ambiguity in this version, though. After the 'except' keyword, you can have a tuple of exceptions:
try: 1/x except (NameError, ZeroDivisionError): print("Can't divide by nothing!")
If the previous value is followed by a comma and the next thing could be a tuple, the parsing is going to get a bit hairy. Either the previous value or the exception_list could be a tuple. I'd rather repeat the word 'except'.
Sorry, I don't catch the point. If I needed to use a complex exception spec (a tuple-like) and/or a tuple as the "default" expression -- I'd just do it: some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam') I see no ambiguity here.
I'm happy with them being optional in cases where it's unambiguous, but I'd be okay with mandating them to disambiguate. After all, if the language says they're optional, style guides have the option of mandating; but if the language mandates, the freedom is gone.
Sometimes -- in syntax -- freedom is a bad thing. There are good reasons that 1-argument function calls have the form: "func(arg)" and not "func arg" -- though the latter could still be unambiguous.
More complex (though probably not very realistic) example:
msg = (cache[k] except ( LookupError: backend.read() except ( OSError: 'resource not available')) if check_permission(k) else 'access not allowed' ) except (Exception: 'internal error occurred')
Fairly unrealistic, as the backend.read() call won't get put into the
The example was about syntax of nested expressions, not about this or that use case.
cache (and you can't put the final msg into the cache, as 'resource not available' sounds temporary and thus non-cached), not to mention that swallowing Exception to just return a constant string seems like a really REALLY bad idea! :) But this highlights another big issue: How should a complex except expression be laid out? With the statement form, it's pretty clear: first you do the line(s) that you're trying, then you have your except clauses, no more than one on any given line, and then you have your else, and your finally, if you have either. Everything's on separate lines. What about here? If it all fits on one line, great! But it often won't (the name "ZeroDivisionError" is over a fifth of your typical line width, all on its own), and then where do you break it? Not technically part of the proposal, but it's going to be an issue.
If we talk about *one* complex except expression, in terms of the
syntax variant I proposed, the natural layout form seems to be:
On Mon, Feb 17, 2014 at 7:39 AM, Jan Kaliszewski
Sorry, I don't catch the point. If I needed to use a complex exception spec (a tuple-like) and/or a tuple as the "default" expression -- I'd just do it:
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
I see no ambiguity here.
Maybe not, but the only thing preventing that from parsing as a tuple containing two tuples is the colon a bit further along. That might be sufficient for the lexer (in the same way that, for instance, function arguments can contain tuples), but I suspect it may be confusing for humans. ChrisA
On Mon, Feb 17, 2014 at 7:39 AM, Jan Kaliszewski
wrote: Sorry, I don't catch the point. If I needed to use a complex exception spec (a tuple-like) and/or a tuple as the "default" expression -- I'd just do it:
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
I see no ambiguity here.
To make it parseable, it would probably be necessary to disallow commas in the expression following the colon (i.e. it would be a 'test' rather than a 'testlist' in terms of the grammar). -- Greg
On Mon, Feb 17, 2014 at 9:33 AM, Greg Ewing
On Mon, Feb 17, 2014 at 7:39 AM, Jan Kaliszewski
wrote: Sorry, I don't catch the point. If I needed to use a complex exception spec (a tuple-like) and/or a tuple as the "default" expression -- I'd just do it:
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
I see no ambiguity here.
To make it parseable, it would probably be necessary to disallow commas in the expression following the colon (i.e. it would be a 'test' rather than a 'testlist' in terms of the grammar).
Do you mean that there may not be a comma, or that commas must be surrounded by parens? I wouldn't mind *too* much if it's the latter, but I still think this could be confusing. Imagine if the exception list isn't literals, but comes from somewhere else (which is perfectly legal). How do you eyeball it and see that this is now a new except clause? ChrisA
Chris Angelico wrote:
Do you mean that there may not be a comma, or that commas must be surrounded by parens?
Commas would need to be surroounded by parens.
Imagine if the exception list isn't literals, but comes from somewhere else (which is perfectly legal). How do you eyeball it and see that this is now a new except clause?
I'm not a big fan of this idea either. It seems to me that wanting more than one except clause is going to be *extremely* rare, so it's not worth going out of our way to make it super-concise. -- Greg
On 02/17/2014 03:40 PM, Greg Ewing wrote:
Chris Angelico wrote:
Do you mean that there may not be a comma, or that commas must be surrounded by parens?
Commas would need to be surroounded by parens.
Imagine if the exception list isn't literals, but comes from somewhere else (which is perfectly legal). How do you eyeball it and see that this is now a new except clause?
I'm not a big fan of this idea either. It seems to me that wanting more than one except clause is going to be *extremely* rare, so it's not worth going out of our way to make it super-concise.
I agree, and adding too much too soon, could limit options for later enhancement. In the recent thread on enhanced exceptions, it's suggested we add more information to exceptions. I have a different point of view that would have an effect on this idea. To me, the added information on exceptions, should also be usable, preferable to allow more control on catching exceptions. For example:. value = expr1 except IndexError from foo: expr2 Where the exception caught is one raised in "foo". While other IndexError's are allowed to bubble out. We can't use else... although that would be my first choice if else wasn't already used differently in except and for statements. To explain a bit further, when you get a trace back from an uncaught exception, the last line of the trace back contains the function it happened in. But that info is difficult to get from a caught exception. It's even nicer to be able to use it to specify more precisely what exceptions to catch. Cheers, Ron
On Tue, Feb 18, 2014 at 12:31 PM, Ron Adam
To me, the added information on exceptions, should also be usable, preferable to allow more control on catching exceptions.
For example:.
value = expr1 except IndexError from foo: expr2
Where the exception caught is one raised in "foo". While other IndexError's are allowed to bubble out.
We can't use else... although that would be my first choice if else wasn't already used differently in except and for statements.
To explain a bit further, when you get a trace back from an uncaught exception, the last line of the trace back contains the function it happened in. But that info is difficult to get from a caught exception. It's even nicer to be able to use it to specify more precisely what exceptions to catch.
I'd say that that's tangential to this proposal. Let's look at that in terms of the statement try/except: # Catch everything and swallow it try: foo() except: pass # Catch everything and reraise it (pretty useless actually) try: foo() except: raise # Catch based on class: try: foo() except IndexError: pass except: pass # This bit is implicitly done, if you like # Catch based on something else: try: foo() except Exception as e: if e.foo: pass else: raise except: raise It does make sense to be able to, for instance: # Catch based on something else: try: foo() except IndexError as e if e.key<30: pass One advantage of the currently-favoured syntax for exception expressions is that it'd be easy to add extra syntax to both statement and expression forms, since they're identical (modulo the "try:" at the beginning). So if someone wants to propose any of these hypothetical features, they'd fit fine into the expression version too: try: foo() except as e: pass # Catch everything, and capture the exception try: foo() except OSError not FileNotFoundError: pass # Don't ignore FileNotFound except Exception as e: # log the exception, which might be several lines of code try: foo() except collections.Callable and collections.Iterable as e: # Deal with... exceptions that are both callable and iterable?!?? Okay, so that last one is a bit stupid, but you get the idea. Same thing would work in either context, so it can be raised (pun intended) as a separate proposal. ChrisA
On 18/02/2014 02:08, Chris Angelico wrote:
On Tue, Feb 18, 2014 at 12:31 PM, Ron Adam
wrote: To me, the added information on exceptions, should also be usable, preferable to allow more control on catching exceptions.
For example:.
value = expr1 except IndexError from foo: expr2
Where the exception caught is one raised in "foo". While other IndexError's are allowed to bubble out.
We can't use else... although that would be my first choice if else wasn't already used differently in except and for statements.
To explain a bit further, when you get a trace back from an uncaught exception, the last line of the trace back contains the function it happened in. But that info is difficult to get from a caught exception. It's even nicer to be able to use it to specify more precisely what exceptions to catch. I'd say that that's tangential to this proposal. Let's look at that in terms of the statement try/except:
# Catch everything and swallow it try: foo() except: pass
# Catch everything and reraise it (pretty useless actually) try: foo() except: raise
# Catch based on class: try: foo() except IndexError: pass except: pass # This bit is implicitly done, if you like
# Catch based on something else: try: foo() except Exception as e: if e.foo: pass else: raise except: raise
It does make sense to be able to, for instance:
# Catch based on something else: try: foo() except IndexError as e if e.key<30: pass
One advantage of the currently-favoured syntax for exception expressions is that it'd be easy to add extra syntax to both statement and expression forms, since they're identical (modulo the "try:" at the beginning). So if someone wants to propose any of these hypothetical features, they'd fit fine into the expression version too:
try: foo() except as e: pass # Catch everything, and capture the exception
try: foo() except OSError not FileNotFoundError: pass # Don't ignore FileNotFound except Exception as e: # log the exception, which might be several lines of code
try: foo() except collections.Callable and collections.Iterable as e: # Deal with... exceptions that are both callable and iterable?!??
Okay, so that last one is a bit stupid, but you get the idea. Same thing would work in either context, so it can be raised (pun intended) as a separate proposal. Yes! I love consistency. No new learning curve. Did I not imply in an earlier post that any future syntax added to "except" statements could also be incorporated in "except" expressions? (I did.) (Please be tolerant with me, I've had a few drinks. Thank you. :-) ) Rob Cliffe
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/
----- No virus found in this message. Checked by AVG - www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6600 - Release Date: 02/17/14
On Tue, Feb 18, 2014 at 1:32 PM, Rob Cliffe
Yes! I love consistency. No new learning curve. Did I not imply in an earlier post that any future syntax added to "except" statements could also be incorporated in "except" expressions? (I did.) (Please be tolerant with me, I've had a few drinks. Thank you. :-) )
Yeah. That point is a strong one in favour of the "except Exception [as e]: expr" notation. ChrisA
On 18/02/2014 02:35, Chris Angelico wrote:
On Tue, Feb 18, 2014 at 1:32 PM, Rob Cliffe
wrote: Yes! I love consistency. No new learning curve. Did I not imply in an earlier post that any future syntax added to "except" statements could also be incorporated in "except" expressions? (I did.) (Please be tolerant with me, I've had a few drinks. Thank you. :-) ) Yeah. That point is a strong one in favour of the "except Exception [as e]: expr" notation. I agree. (Of course.) RobC
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/
----- No virus found in this message. Checked by AVG - www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6600 - Release Date: 02/17/14
16.02.2014 23:33, Greg Ewing wrote:
On Mon, Feb 17, 2014 at 7:39 AM, Jan Kaliszewski
wrote: Sorry, I don't catch the point. If I needed to use a complex exception spec (a tuple-like) and/or a tuple as the "default" expression -- I'd just do it:
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
I see no ambiguity here.
To make it parseable, it would probably be necessary to disallow commas in the expression following the colon (i.e. it would be a 'test' rather than a 'testlist' in terms of the grammar).
I believe that it would be somehow similar to what is allowed to be keys and values in dict literals (of course modulo the fact that here only except-like exception specification is allowed as a "key" [exception class or tuple of exception classes], and that an additional optional 'as' clause can be used...). In a way, it can be seen as a *kind of* mapping: expression specs to "default" values... So *some* similarity to a dict literal is not a completely random accident. Cheers. *j
16.02.2014 23:19, Chris Angelico wrote:
On Mon, Feb 17, 2014 at 7:39 AM, Jan Kaliszewski
wrote: Sorry, I don't catch the point. If I needed to use a complex exception spec (a tuple-like) and/or a tuple as the "default" expression -- I'd just do it:
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
I see no ambiguity here.
Maybe not, but the only thing preventing that from parsing as a tuple containing two tuples is the colon a bit further along. That might be sufficient for the lexer (in the same way that, for instance, function arguments can contain tuples), but I suspect it may be confusing for humans.
IMHO, not more confusing than a dict literal containing tuple literals as keys or values... Cheers. *j
On Mon, Feb 17, 2014 at 09:19:22AM +1100, Chris Angelico wrote:
On Mon, Feb 17, 2014 at 7:39 AM, Jan Kaliszewski
wrote: Sorry, I don't catch the point. If I needed to use a complex exception spec (a tuple-like) and/or a tuple as the "default" expression -- I'd just do it:
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
I see no ambiguity here.
Maybe not, but the only thing preventing that from parsing as a tuple containing two tuples is the colon a bit further along. That might be sufficient for the lexer (in the same way that, for instance, function arguments can contain tuples), but I suspect it may be confusing for humans.
It's certainly confusing for me! I'm now strongly leaning towards following the lead of generator expressions, and requiring parens around an except-expression. Like gen expressions, you can leave the parens out if they are already there: it = (calc(x) for x in iterable) # parentheses are mandatory result = sum(calc(x) for x in iterable) # but this is okay So if your except-expression stands alone, or is inside some other unbracketed expression, you need to bracket it: value = (expr except Error: default) mytuple = (1, 2, (expr except Error: default), 3) But if it's already directly surrounded by parentheses, say inside a function call with no other arguments, there is no need to double-up: result = function(expr except Error: default) I think that this will ensure that there is no visual ambiguity when you chain except-expressions. Even if the parser/lexer can disambiguate the expression, it risks being completely impenetrable to the human reader. Take this case, where I'm labelling parts for ease of discussion: # What does this do? expr except SpamError: spam except EggsError: eggs #^^^^^FIRST^^^^^^^^^^^^^^^^ ^^^^^^SECOND^^^^^^^^^^ Does the SECOND except capture exceptions in the evaluation of "spam" only, or in the entire FIRST except? If this question doesn't make sense to you, then please ponder these two examples: ((expr except SpamError: spam) except EggsError: eggs) (expr except SpamError: (spam except EggsError: eggs)) Requiring parens should also avoid the subtle error of leaving out a comma when you have multiple except-clauses: expr except SpamError: spam, except EggsError: eggs #..........................^ is a single expression that catches two exceptions, equivalent to: try: _ = expr except SpamError: _ = spam except EggsError: _ = eggs whereas leaving out the comma: expr except SpamError: spam except EggsError: eggs is *two* except-expressions, equivalent to either this: try: try: _ = expr except SpamError: _ = spam except EggsError: _ = eggs or this: try: _ = expr except SpamError: try: _ = spam except EggsError: _ = eggs depending on how strongly the except binds. The with- and without-comma versions are very subtly different, and consequently a potential bug magnet, from a very subtle and easy-to-make typo. By requiring parens, you can avoid that. The first version becomes: (expr except SpamError: spam, except EggsError: eggs) and dropping the comma is a SyntaxError, while the second version becomes either: ((expr except SpamError: spam) except EggsError: eggs) (expr except SpamError: (spam except EggsError: eggs)) depending on which semantics you want. -- Steven
On 17 February 2014 04:46, Steven D'Aprano
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
I see no ambiguity here.
Maybe not, but the only thing preventing that from parsing as a tuple containing two tuples is the colon a bit further along. That might be sufficient for the lexer (in the same way that, for instance, function arguments can contain tuples), but I suspect it may be confusing for humans.
It's certainly confusing for me!
Just as a data point - with my eyesight and the monitor resolution/font I use, I can't even *see* the colon without squinting. There's an old principle "new syntax should not look like grit on Tim's monitor" that probably applies here :-) Paul
On Mon, Feb 17, 2014 at 07:43:08AM +0000, Paul Moore wrote:
Just as a data point - with my eyesight and the monitor resolution/font I use, I can't even *see* the colon without squinting. There's an old principle "new syntax should not look like grit on Tim's monitor" that probably applies here :-)
I sympathise, but you already have to deal with dicts, slices, and lambda, and with the exception (pun intended) of lambda, they're likely to be much more common than except-expression. -- Steven
On 17 February 2014 08:40, Steven D'Aprano
I sympathise, but you already have to deal with dicts, slices, and lambda, and with the exception (pun intended) of lambda, they're likely to be much more common than except-expression.
I should have expanded :-) (See? I also have to deal with smilies!) With dicts slices and lambdas, the colon is essentially not so important for the *human* reader, although it is needed for the parser. If dicts were simply {key, value, key, value, ...} they would still make sense to the human reader, just by pairing things up and by the fact that anyone writing something complicate would lay it out readably. When discussing new syntax, it becomes very easy to focus on the corner cases and how to make them unambiguous and/or clearer than the alternatives. In my view, that's not the point - what matters is whether the syntax cleanly expresses the simpler cases that are the ones motivating the change in the first place. I'm more than happy to relegate the corner cases to a status of "well, it works, but you should be using the statement form when it's this complex anyway". My point with the example I quoted (reproduced here for reference)
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
is precisely that - the subtle change in meaning based on a relatively hard to spot colon is *not* something that matters when debating the relative merits of syntax proposals, because no-one should be writing code like this in the first place. The parallel with dicts is misleading, as you can't write my_dict = {1,2:3,4:5,6,7:8} - first of all, you have to parenthesize the relevant tuples, and secondly nobody sane would write it like that, they'd use layout to make the intent clear to the human reader). Anyway, I don't want to beat up on one example. I'm just trying to make the points 1. Let's worry about making the basic case as readable as possible, rather than the complex cases 2. Punctuation for disambiguation can be a problem - I'd prefer it if the keywords stood by themselves in this case 3. Needing extra parentheses to disambiguate (or even requiring them - consider yield expressions or generator expressions) is not a bad thing, nor is needing to make complex cases multi-line - human readability is more important than using the minimum syntax that the grammar requires. Paul
W dniu 17.02.2014 10:56, Paul Moore napisał(a): [...]
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
is precisely that - the subtle change in meaning based on a relatively hard to spot colon [...]
Please note that in case of the cited example (of the syntax variant proposed by me, i.e. the parens-after-except one) colon does not provide a subtle change but constitutes the whole expression syntax -- without the colon(s) it would just cause SytaxError. Cheers. *j
17.02.2014 15:10, Jan Kaliszewski wrote: [...]
Please note that in case of the cited example (of the syntax variant proposed by me, i.e. the parens-after-except one) colon does not provide a subtle change but constitutes the whole expression syntax [...]
Together with the 'except' keyword of course. *j
On Mon, Feb 17, 2014 at 09:56:21AM +0000, Paul Moore wrote:
On 17 February 2014 08:40, Steven D'Aprano
wrote: I sympathise, but you already have to deal with dicts, slices, and lambda, and with the exception (pun intended) of lambda, they're likely to be much more common than except-expression.
I should have expanded :-) (See? I also have to deal with smilies!)
With dicts slices and lambdas, the colon is essentially not so important for the *human* reader, although it is needed for the parser. If dicts were simply {key, value, key, value, ...} they would still make sense to the human reader, just by pairing things up
Which can be quite tedious and error-prone, especially if you follow the PEP 8 recommendation to *not* line up columns in your data. Quickly now, is None a key or a value? d = {258, 261, 71, 201, 3, 204, 907, 210, 4, 513, 23, 8, 219, None, 31, 225, 528, 39, 234, 231, 1237, 47, 640, 243, 246, 55, 149, 255, 252, 13} I suppose Python might have used spaces to seperate slice fields: mylist[start end stride] although that does make it awkward to do slices using defaults: mylist[ -1] # default start=0 mylist[1 ] # default end=len of the sequence
and by the fact that anyone writing something complicate would lay it out readably.
Oh my, have you lived a sheltered life! *wink* [...]
My point with the example I quoted (reproduced here for reference)
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
is precisely that - the subtle change in meaning based on a relatively hard to spot colon is *not* something that matters when debating the relative merits of syntax proposals, because no-one should be writing code like this in the first place.
That's not the syntax I'm pushing for. The form I want to see requires the `except` keyword to be repeated: (some_io() except FileNotFoundError: (1, 2, 3), except ValueError, TypeError: 'spam') Brackets around the ValueError, TypeError pair might be allowed: (some_io() except FileNotFoundError: (1, 2, 3), except (ValueError, TypeError): 'spam')
1. Let's worry about making the basic case as readable as possible, rather than the complex cases
Agreed. But I think the basic case is very readable, and the complicated case less so only because it is complicated, not because of the syntax.
2. Punctuation for disambiguation can be a problem - I'd prefer it if the keywords stood by themselves in this case
Yes but no but yes but no but yes but ... There are very few cases in Python of using space as mandatory separator. All the cases I can think apply only to keywords: class C ... def func ... import this, that, other del a, b, c but not import this that other del a b c The most relevant case to this is, I think, lambda, where we write: lambda parameter_list : expression rather than (say): lambda (parameter_list) expression with mandatory brackets around the parameter list to separate it from the expression. Using the existing syntax as a guideline, I get this: expression except exception_list : default_expression rather than this: expression except (exception_list) default_expression
3. Needing extra parentheses to disambiguate (or even requiring them - consider yield expressions or generator expressions) is not a bad thing, nor is needing to make complex cases multi-line - human readability is more important than using the minimum syntax that the grammar requires.
I agree with this. -- Steven
On Tue, Feb 18, 2014 at 7:50 AM, Steven D'Aprano
Brackets around the ValueError, TypeError pair might be allowed:
(some_io() except FileNotFoundError: (1, 2, 3), except (ValueError, TypeError): 'spam')
I'd say that the parens are definitely allowed, and might be required. (They're currently required in the statement form, but I think that's just to distinguish from the old pre-'as' syntax.) ChrisA
17.02.2014 05:46, Steven D'Aprano wrote:
On Mon, Feb 17, 2014 at 09:19:22AM +1100, Chris Angelico wrote:
On Mon, Feb 17, 2014 at 7:39 AM, Jan Kaliszewski
wrote: Sorry, I don't catch the point. If I needed to use a complex exception spec (a tuple-like) and/or a tuple as the "default" expression -- I'd just do it:
some_io() except (FileNotFoundError: (1, 2, 3), (ValueError, TypeError): 'spam')
I see no ambiguity here.
Maybe not, but the only thing preventing that from parsing as a tuple containing two tuples is the colon a bit further along. That might be sufficient for the lexer (in the same way that, for instance, function arguments can contain tuples), but I suspect it may be confusing for humans.
It's certainly confusing for me!
I'm now strongly leaning towards following the lead of generator expressions, and requiring parens around an except-expression. Like gen expressions, you can leave the parens out if they are already there:
it = (calc(x) for x in iterable) # parentheses are mandatory result = sum(calc(x) for x in iterable) # but this is okay
So if your except-expression stands alone, or is inside some other unbracketed expression, you need to bracket it:
value = (expr except Error: default) mytuple = (1, 2, (expr except Error: default), 3)
But if it's already directly surrounded by parentheses, say inside a function call with no other arguments, there is no need to double-up:
result = function(expr except Error: default)
Now, I am confused a bit. First you cite a discussion about the paren-after-except syntax variant (the one I proposed) but then you discuss the syntax variant from the PEP draft by Chris. Please note that these variants are different. See below...
I think that this will ensure that there is no visual ambiguity when you chain except-expressions. Even if the parser/lexer can disambiguate the expression, it risks being completely impenetrable to the human reader. Take this case, where I'm labelling parts for ease of discussion:
# What does this do? expr except SpamError: spam except EggsError: eggs #^^^^^FIRST^^^^^^^^^^^^^^^^ ^^^^^^SECOND^^^^^^^^^^
Does the SECOND except capture exceptions in the evaluation of "spam" only, or in the entire FIRST except? If this question doesn't make sense to you, then please ponder these two examples:
((expr except SpamError: spam) except EggsError: eggs) (expr except SpamError: (spam except EggsError: eggs))
Requiring parens should also avoid the subtle error of leaving out a comma when you have multiple except-clauses:
expr except SpamError: spam, except EggsError: eggs #..........................^
is a single expression that catches two exceptions, equivalent to:
try: _ = expr except SpamError: _ = spam except EggsError: _ = eggs
whereas leaving out the comma:
expr except SpamError: spam except EggsError: eggs
is *two* except-expressions, equivalent to either this:
try: try: _ = expr except SpamError: _ = spam except EggsError: _ = eggs
or this:
try: _ = expr except SpamError: try: _ = spam except EggsError: _ = eggs
depending on how strongly the except binds.
The with- and without-comma versions are very subtly different, and consequently a potential bug magnet, from a very subtle and easy-to-make typo. By requiring parens, you can avoid that. The first version becomes:
(expr except SpamError: spam, except EggsError: eggs)
and dropping the comma is a SyntaxError, while the second version becomes either:
((expr except SpamError: spam) except EggsError: eggs) (expr except SpamError: (spam except EggsError: eggs))
depending on which semantics you want.
On the other hand the paren-after-except variant makes it unambiguous: expr except (SpamError: spam, EggsError: eggs) is clearly different from: expr except (SpamError: spam) except (EggsError: eggs) ...which is the same as: (expr except (SpamError: spam)) except (EggsError: eggs) ...as neither "(SpamError: spam)" nor "except (SpamError: spam)" are valid expressions themselves. But only "EXPRESSION except (SpamError: spam)" taken as a whole makes up a valid expression. Cheers. *j
Steven D'Aprano wrote:
I'm now strongly leaning towards following the lead of generator expressions, and requiring parens around an except-expression.
I think that this will ensure that there is no visual ambiguity when you chain except-expressions.
I think we should keep in mind that we're talking about what will be a somewhat rarely used construct, and that the overwhelming majority of those rare uses will be the simplest possible ones, with just a single 'except' clause and a very simple expression for the exceptional case, e.g. things[i] except IndexError: None We should concentrate on making that case as clear and uncluttered as possible, and not worry overmuch about how things look in more complicated cases that will hardly ever arise in practice. -- Greg
Chris Angelico wrote:
I'm happy with them being optional in cases where it's unambiguous, but I'd be okay with mandating them to disambiguate.
I'm not sure I like the idea of parens being optional in an "odd" place, e.g. just before 'except'. I feel that such departures from the usual conventions should be a rigid feature of the syntax -- either mandatory or forbidden. -- Greg
Steven D'Aprano wrote:
On Sat, Feb 15, 2014 at 11:20:13AM +1300, Greg Ewing wrote:
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)
Certainly not! pass implies that *no return result is generated at all*, which is not possible in Python.
Well, it is, kind of -- an implicit None is produced when you don't specify a return value for a function; this is a similar thing. Would it help if it were *only* allow it in a context where the value of the expression is going to be ignored? -- Greg
On Sun, Feb 16, 2014 at 10:37 AM, Greg Ewing
Steven D'Aprano wrote:
On Sat, Feb 15, 2014 at 11:20:13AM +1300, Greg Ewing wrote:
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)
Certainly not! pass implies that *no return result is generated at all*, which is not possible in Python.
Well, it is, kind of -- an implicit None is produced when you don't specify a return value for a function; this is a similar thing.
Would it help if it were *only* allow it in a context where the value of the expression is going to be ignored?
That's nothing to do with the 'pass' keyword. def foo(): print("Hello!") This will return None, and it doesn't have 'pass'. def foo(x): if x: pass return 42 This will not, regardless of the value of x. Python's 'pass' means 'do nothing', in a place where you contextually need to do something. ChrisA
On 02/15/2014 10:11 AM, Steven D'Aprano wrote:
Certainly not! pass implies that *no return result is generated at all*, [...]
Don't be silly. def have_a_mint(some, args, here): # flesh this out later pass Does anybody really think that that function will not return None? -- ~Ethan~
On Mon, Feb 17, 2014 at 1:47 PM, Ethan Furman
On 02/15/2014 10:11 AM, Steven D'Aprano wrote:
Certainly not! pass implies that *no return result is generated at all*, [...]
Don't be silly.
def have_a_mint(some, args, here): # flesh this out later pass
Does anybody really think that that function will not return None?
Of course it'll return None, but that's nothing to do with the 'pass'. The keyword 'pass' doesn't generate any return result at all. ChrisA
On 02/16/2014 08:04 PM, Chris Angelico wrote:
On Mon, Feb 17, 2014 at 1:47 PM, Ethan Furman wrote:
On 02/15/2014 10:11 AM, Steven D'Aprano wrote:
Certainly not! pass implies that *no return result is generated at all*, [...]
Don't be silly.
def have_a_mint(some, args, here): # flesh this out later pass
Does anybody really think that that function will not return None?
Of course it'll return None, but that's nothing to do with the 'pass'. The keyword 'pass' doesn't generate any return result at all.
Precisely. -- ~Ethan~
Chris Angelico wrote:
On Mon, Feb 17, 2014 at 1:47 PM, Ethan Furman
wrote: def have_a_mint(some, args, here): # flesh this out later pass
Does anybody really think that that function will not return None?
Of course it'll return None, but that's nothing to do with the 'pass'. The keyword 'pass' doesn't generate any return result at all.
Function return values are a red herring here. The point is to allow the programmer to directly express the intent of the code without having to introduce spurious values that will be ignored. There's no logical difference between not generating a result at all, and generating a result of None and then throwing it away. The same thing applies here: menu.remove(mint) except ValueError: pass This says exactly what the programmer means: "Remove mint from the menu if it's there, otherwise do nothing." An alternative would be to allow the exceptional value part to be omitted altogether: menu.remove(mint) except ValueError but that looks incomplete, making you wonder if the programmer forgot something. Either way, this would be allowed *only* for top level expressions whose value is ignored. In any context where the value of the expression is used for something, the exceptional value would have to be spelled out explicitly. -- Greg
On Tue, Feb 18, 2014 at 9:26 AM, Greg Ewing
There's no logical difference between not generating a result at all, and generating a result of None and then throwing it away.
The same thing applies here:
menu.remove(mint) except ValueError: pass
This says exactly what the programmer means: "Remove mint from the menu if it's there, otherwise do nothing."
In a statement context, it's possible to say "otherwise do nothing". An if without an else does this, as does putting 'pass' in certain places. But in an expression context, there are 'two possibility'. Either some kind of value is returned, or an exception is raised. (At least, I don't think there are any other options. Harry Hoo, not the Spanish Inquisition.) What should happen here: func(menu.remove(mint) except ValueError: pass) If remove() raises ValueError, should func be called? If so, with what argument? (Note that it's because English is happy with "passing" arguments to functions that I was happy with the notation "except ValueError pass None", but that's using pass as a syntactic element, in place of the colon.) ChrisA
On 02/17/2014 03:54 PM, Chris Angelico wrote:
On Tue, Feb 18, 2014 at 9:26 AM, Greg Ewing
wrote: There's no logical difference between not generating a result at all, and generating a result of None and then throwing it away.
The same thing applies here:
menu.remove(mint) except ValueError: pass
This says exactly what the programmer means: "Remove mint from the menu if it's there, otherwise do nothing."
In a statement context, it's possible to say "otherwise do nothing". An if without an else does this, as does putting 'pass' in certain places.
But in an expression context, there are 'two possibility'. Either some kind of value is returned, or an exception is raised. (At least, I don't think there are any other options. Harry Hoo, not the Spanish Inquisition.) What should happen here:
func(menu.remove(mint) except ValueError: pass)
If remove() raises ValueError, should func be called? If so, with what argument? (Note that it's because English is happy with "passing" arguments to functions that I was happy with the notation "except ValueError pass None", but that's using pass as a syntactic element, in place of the colon.)
Yes, func should be called, and it should be called with no arguments. -- ~Ethan~
On Tue, Feb 18, 2014 at 11:20 AM, Ethan Furman
On 02/17/2014 03:54 PM, Chris Angelico wrote:
But in an expression context, there are 'two possibility'. Either some kind of value is returned, or an exception is raised. (At least, I don't think there are any other options. Harry Hoo, not the Spanish Inquisition.) What should happen here:
func(menu.remove(mint) except ValueError: pass)
If remove() raises ValueError, should func be called? If so, with what argument? (Note that it's because English is happy with "passing" arguments to functions that I was happy with the notation "except ValueError pass None", but that's using pass as a syntactic element, in place of the colon.)
Yes, func should be called, and it should be called with no arguments.
Ooh. That's an interesting one. I'm not sure how that would go in terms of readability, but it is an interesting concept. Very interesting concept. Not sure that I like it, but ... the mind does like the idea ... in a weird and dangerous way. def throw(ex): raise ex some_func(1, 2, (x if x is not None else throw(SyntaxError)) except SyntaxError: pass) So if x is None, it doesn't get passed at all. Useful feature... wordy and clunky spelling... no, I'm now sure that I do not like this. But it's amusing! ChrisA
On 2014-02-18 00:48, Chris Angelico wrote:
On Tue, Feb 18, 2014 at 11:20 AM, Ethan Furman
wrote: On 02/17/2014 03:54 PM, Chris Angelico wrote:
But in an expression context, there are 'two possibility'. Either some kind of value is returned, or an exception is raised. (At least, I don't think there are any other options. Harry Hoo, not the Spanish Inquisition.) What should happen here:
func(menu.remove(mint) except ValueError: pass)
If remove() raises ValueError, should func be called? If so, with what argument? (Note that it's because English is happy with "passing" arguments to functions that I was happy with the notation "except ValueError pass None", but that's using pass as a syntactic element, in place of the colon.)
Yes, func should be called, and it should be called with no arguments.
Ooh. That's an interesting one. I'm not sure how that would go in terms of readability, but it is an interesting concept. Very interesting concept. Not sure that I like it, but ... the mind does like the idea ... in a weird and dangerous way.
def throw(ex): raise ex
some_func(1, 2, (x if x is not None else throw(SyntaxError)) except SyntaxError: pass)
So if x is None, it doesn't get passed at all. Useful feature... wordy and clunky spelling... no, I'm now sure that I do not like this. But it's amusing!
That would be clearer as: some_func(1, 2, (x if x is not None else pass)) for some value of "clearer". :-)
On Tue, Feb 18, 2014 at 1:11 PM, MRAB
def throw(ex): raise ex
some_func(1, 2, (x if x is not None else throw(SyntaxError)) except SyntaxError: pass)
So if x is None, it doesn't get passed at all. Useful feature... wordy and clunky spelling... no, I'm now sure that I do not like this. But it's amusing!
That would be clearer as:
some_func(1, 2, (x if x is not None else pass))
for some value of "clearer". :-)
That depends on the "if/else pass" syntax also being added. The convolutions above mean that only "except: pass" is needed. :) Actually, all it requires is that there be some kind of magic object called "pass" which is whatever the function's default is. Then it would behave exactly as described, bar the bit about actually passing less args - it'd require some sort of default. I'm still not sure it's a good idea, though :) ChrisA
On 2014-02-18 00:20, Ethan Furman wrote:
On 02/17/2014 03:54 PM, Chris Angelico wrote:
On Tue, Feb 18, 2014 at 9:26 AM, Greg Ewing
wrote: There's no logical difference between not generating a result at all, and generating a result of None and then throwing it away.
The same thing applies here:
menu.remove(mint) except ValueError: pass
This says exactly what the programmer means: "Remove mint from the menu if it's there, otherwise do nothing."
In a statement context, it's possible to say "otherwise do nothing". An if without an else does this, as does putting 'pass' in certain places.
But in an expression context, there are 'two possibility'. Either some kind of value is returned, or an exception is raised. (At least, I don't think there are any other options. Harry Hoo, not the Spanish Inquisition.) What should happen here:
func(menu.remove(mint) except ValueError: pass)
If remove() raises ValueError, should func be called? If so, with what argument? (Note that it's because English is happy with "passing" arguments to functions that I was happy with the notation "except ValueError pass None", but that's using pass as a syntactic element, in place of the colon.)
Yes, func should be called, and it should be called with no arguments.
In that case, is: func(foo if condition else pass) allowed (being like "func(foo) if condition else func()")? And, in fact, is: func(pass) also allowed (meaning "func()")?
On Tue, Feb 18, 2014 at 11:57 AM, MRAB
In that case, is:
func(foo if condition else pass)
allowed (being like "func(foo) if condition else func()")?
And, in fact, is:
func(pass)
also allowed (meaning "func()")?
Oh, and: func(pass, 2, 3) should be the same as func(2, 3) right? ChrisA
On 02/17/2014 04:57 PM, MRAB wrote:
On 2014-02-18 00:20, Ethan Furman wrote:
On 02/17/2014 03:54 PM, Chris Angelico wrote:
On Tue, Feb 18, 2014 at 9:26 AM, Greg Ewing
wrote: There's no logical difference between not generating a result at all, and generating a result of None and then throwing it away.
The same thing applies here:
menu.remove(mint) except ValueError: pass
This says exactly what the programmer means: "Remove mint from the menu if it's there, otherwise do nothing."
In a statement context, it's possible to say "otherwise do nothing". An if without an else does this, as does putting 'pass' in certain places.
But in an expression context, there are 'two possibility'. Either some kind of value is returned, or an exception is raised. (At least, I don't think there are any other options. Harry Hoo, not the Spanish Inquisition.) What should happen here:
func(menu.remove(mint) except ValueError: pass)
If remove() raises ValueError, should func be called? If so, with what argument? (Note that it's because English is happy with "passing" arguments to functions that I was happy with the notation "except ValueError pass None", but that's using pass as a syntactic element, in place of the colon.)
Yes, func should be called, and it should be called with no arguments.
In that case, is:
func(foo if condition else pass)
allowed (being like "func(foo) if condition else func()")?
And, in fact, is:
func(pass)
also allowed (meaning "func()")?
I'm not saying it's a good idea, just that it's the natural conclusion from allowing pass. I don't care for it, myself. Just use None, since that's Python "No value" value. -- ~Ethan~
On 2014-02-18 01:30, Ethan Furman wrote:
On 02/17/2014 04:57 PM, MRAB wrote:
On 2014-02-18 00:20, Ethan Furman wrote:
On 02/17/2014 03:54 PM, Chris Angelico wrote:
On Tue, Feb 18, 2014 at 9:26 AM, Greg Ewing
wrote: There's no logical difference between not generating a result at all, and generating a result of None and then throwing it away.
The same thing applies here:
menu.remove(mint) except ValueError: pass
This says exactly what the programmer means: "Remove mint from the menu if it's there, otherwise do nothing."
In a statement context, it's possible to say "otherwise do nothing". An if without an else does this, as does putting 'pass' in certain places.
But in an expression context, there are 'two possibility'. Either some kind of value is returned, or an exception is raised. (At least, I don't think there are any other options. Harry Hoo, not the Spanish Inquisition.) What should happen here:
func(menu.remove(mint) except ValueError: pass)
If remove() raises ValueError, should func be called? If so, with what argument? (Note that it's because English is happy with "passing" arguments to functions that I was happy with the notation "except ValueError pass None", but that's using pass as a syntactic element, in place of the colon.)
Yes, func should be called, and it should be called with no arguments.
In that case, is:
func(foo if condition else pass)
allowed (being like "func(foo) if condition else func()")?
And, in fact, is:
func(pass)
also allowed (meaning "func()")?
I'm not saying it's a good idea, just that it's the natural conclusion from allowing pass.
I don't care for it, myself. Just use None, since that's Python "No value" value.
IMHO, if "pass" were allowed, it should be as part of a statement, not an expression, so: menu.remove(mint) except ValueError: pass would be short for: try: menu.remove(mint) except ValueError: pass but: func(menu.remove(mint) except ValueError: pass) wouldn't be allowed.
On 17/02/2014 22:26, Greg Ewing wrote:
Chris Angelico wrote:
On Mon, Feb 17, 2014 at 1:47 PM, Ethan Furman
wrote: def have_a_mint(some, args, here): # flesh this out later pass
Does anybody really think that that function will not return None?
Of course it'll return None, but that's nothing to do with the 'pass'. The keyword 'pass' doesn't generate any return result at all.
Function return values are a red herring here. The point is to allow the programmer to directly express the intent of the code without having to introduce spurious values that will be ignored.
There's no logical difference between not generating a result at all, and generating a result of None and then throwing it away.
The same thing applies here:
menu.remove(mint) except ValueError: pass Yes, this does read better than (say) "except ValueError: None" or even "except ValueError: doNothing()". I'm starting to like it, although it introduces an inconsistency between expressions whose values are used and those whose values are not used.
This says exactly what the programmer means: "Remove mint from the menu if it's there, otherwise do nothing."
An alternative would be to allow the exceptional value part to be omitted altogether:
menu.remove(mint) except ValueError
but that looks incomplete, making you wonder if the programmer forgot something.
Either way, this would be allowed *only* for top level expressions whose value is ignored. Yes of course, otherwise it would make no sense. Rob Cliffe In any context where the value of the expression is used for something, the exceptional value would have to be spelled out explicitly.
On Tue, Feb 18, 2014 at 11:50 AM, Rob Cliffe
On 17/02/2014 22:26, Greg Ewing wrote:
There's no logical difference between not generating a result at all, and generating a result of None and then throwing it away.
The same thing applies here:
menu.remove(mint) except ValueError: pass
Yes, this does read better than (say) "except ValueError: None" or even "except ValueError: doNothing()". I'm starting to like it, although it introduces an inconsistency between expressions whose values are used and those whose values are not used.
This says exactly what the programmer means: "Remove mint from the menu if it's there, otherwise do nothing."
Either way, this would be allowed *only* for top level expressions whose value is ignored.
Yes of course, otherwise it would make no sense.
The trouble with this is that it blurs the line between a statement and an expression. Normally, "menu.remove(mint)" is an expression, it has a value. In the interactive interpreter, any non-None value returned will be printed. Adding "except ValueError: pass" to the end... might make it not an expression any more. Or does it always make it not an expression? This can be spelled: try: f() except ValueError: pass which is only two lines (down from four for a "classic" try block). I'm not sure that going down to one is worth the confusion. You can keep it as an expression by just returning None, or you can make it a statement in two lines. But if you like, I can add another section to the PEP mentioning this. ChrisA
Rob Cliffe wrote:
On 17/02/2014 22:26, Greg Ewing wrote:
menu.remove(mint) except ValueError: pass
I'm starting to like it, although it introduces an inconsistency between expressions whose values are used and those whose values are not used.
If it helps, you could think of this form of the syntax as a statement rather than an expression. It's an abbreviation for try: menu.remove(mint) except ValueError: pass which you wouldn't otherwise be able to write on one line. -- Greg
On Feb 17, 2014, at 22:37, Greg Ewing
Rob Cliffe wrote:
On 17/02/2014 22:26, Greg Ewing wrote:
menu.remove(mint) except ValueError: pass I'm starting to like it, although it introduces an inconsistency between expressions whose values are used and those whose values are not used.
If it helps, you could think of this form of the syntax as a statement rather than an expression.
That helps a lot, in that it explains exactly what's wrong with the idea. The except expression is useful because it lets you handle exceptions in any expression context--a function argument, whatever. Code that would otherwise be clunky to write becomes simple. The fact that it also lets you avoid a newline or two is not the point, and is barely worth mentioning as an added bonus. Adding pass to the except expression does not allow any code to become simpler. The _only_ thing it does is allow you to avoid a newline. And it doesn't even do _that_:
It's an abbreviation for
try: menu.remove(mint) except ValueError: pass
which you wouldn't otherwise be able to write on one line.
Sure you would: menu.remove(mint) except ValueError: None Exactly as short as the "pass" version, and without requiring the user or the compiler (or the linter, font-lock mode, etc.) to go through the hurdles of realizing that this is happening at the top level of an expression statement, rather than just in any expression, and therefore the value is guaranteed ignored (which isn't actually even true, given things like _ at the interactive interpreter, but we have to _pretend_ it is to even make sense of the idea), and therefore we use different syntax with a pass keyword in place of a value. OK, it does require one more keystroke to capitalize the N, but if that bothers you, write it this way: menu.remove(mint) except ValueError: 0
I think the idea of using commas for grouping in an except expression is a bad idea. To be clear when I say grouping what I mean is that as defined in the proposal when the comma is omitted between two except clauses, they form two separate except blocks and including the comma groups them into one. Compare: A <except-clause1>, <except-clause2> <except-clause3> A <except-clause1> <except-clause2>, <except-clause3> Here's color-coding to show the expressions we would have. A <except-clause1>, <except-clause2> <except-clause3> A <except-clause1> <except-clause2>, <except-clause3> This is nothing like how commas are used anywhere else in the language. I think it much better to use parenthesis for grouping: (A <except-clause1> <except-clause2>) <except-clause3> (A <except-clause1>) <except-clause2> <except-clause3> --- Bruce Learn how hackers think: http://j.mp/gruyere-security
On Sun, Feb 16, 2014 at 06:47:20PM -0800, Ethan Furman wrote:
On 02/15/2014 10:11 AM, Steven D'Aprano wrote:
Certainly not! pass implies that *no return result is generated at all*, [...]
Don't be silly.
def have_a_mint(some, args, here): # flesh this out later pass
Does anybody really think that that function will not return None?
But it isn't the *pass* that generates the "return None". It's falling out the bottom of the function that generates the "return None". Do you intend for this function to return None? def trouble_down_mine(args): pass return "NOBODY expects the Spanish Inquisition!!!" "pass" does not mean "return from this function", as I'm sure you know. That's why I object to using "pass" in a form that implies "return None". It is a placeholder, used to satisfy the lexer/parser, and gets compiled out. As far as the compiler is concerned, "pass" simply disappears when compiled: py> from dis import dis py> dis("pass; spam") 1 0 LOAD_NAME 0 (spam) 3 POP_TOP 4 LOAD_CONST 0 (None) 7 RETURN_VALUE You may notice that the compiler currently adds a (redundant?) "return None" to everything(?). That includes "pass", and nothing at all: py> dis("pass") 1 0 LOAD_CONST 0 (None) 3 RETURN_VALUE py> dis("") 1 0 LOAD_CONST 0 (None) 3 RETURN_VALUE -- Steven
spir 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.]
I don't think it shows that at all. Which is the normal case and which is the exceptional one depends entirely on how the condition is expressed. -- Greg
On 02/13/2014 11:25 PM, Greg Ewing wrote:
spir 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.]
I don't think it shows that at all. Which is the normal case and which is the exceptional one depends entirely on how the condition is expressed.
Say it differently: the condition should single out the special case, not the normal one; d
Am 14.02.2014 08:41, schrieb spir:
On 02/13/2014 11:25 PM, Greg Ewing wrote:
spir 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.]
I don't think it shows that at all. Which is the normal case and which is the exceptional one depends entirely on how the condition is expressed.
Say it differently: the condition should single out the special case, not the normal one;
And that is also just your opinion. Georg
Chris Angelico wrote:
phone = addressbook[name] except KeyError or "Unknown"
How about phone = addressbook[name] except "Unknown" if KeyError Yes, it puts things in a different order from the except statement, but I don't think that's any worse than the if-expression being ordered differently from the if-statement, and it has the same benefit, i.e. reading more smoothly. -- Greg
As has been noted earlier in the thread when someone proposed except...if -
there are parsing issues due to if..else already being a ternary expression.
On Thu Feb 13 2014 at 1:18:40 PM, Greg Ewing
Chris Angelico wrote:
phone = addressbook[name] except KeyError or "Unknown"
How about
phone = addressbook[name] except "Unknown" if KeyError
Yes, it puts things in a different order from the except statement, but I don't think that's any worse than the if-expression being ordered differently from the if-statement, and it has the same benefit, i.e. reading more smoothly.
-- 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/
Ben Finney writes:
Ben Finney
writes: Ram Rachum
writes:
Here's an idea that would help shortening code.
Shortening code is not a win in Python in general. Other things equal, OK, but clarity comes first. ITSM Raymond's comment about simplifying *other* APIs is the right way to get support from python-dev.
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"
Why not spell it the same way as in a try statement? response_text = request('http://whatever.com').text except HttpError as "can't access data" The "as" clause would be required, so "as" always binds to the immediately preceding "except", and iterated it should associate as "(this except ErrorA as that) except ErrorB as other" rather than "this except ErrorA as (that except ErrorB as other)" IMO. I don't think it reads very well, though. The statement form (1) emphasizes the main operation compared to the "try", and (2) suggests that catching an exception is quite a heavy operation compared to execution without an exception, which is true.
That is more obscure, to my eye, than laying out the control branches:
try: response_text = request('http://whatever.com').text except HttpError: "Can't access data"
This has incorrect semantics. The correct semantics would be try: response_text = request('http://whatever.com').text except HttpError: response_text = "Can't access data" I assume.
On Thu, Feb 13, 2014 at 3:57 PM, Stephen J. Turnbull
Why not spell it the same way as in a try statement?
response_text = request('http://whatever.com').text except HttpError as "can't access data"
The "as" clause would be required, so "as" always binds to the immediately preceding "except", and iterated it should associate as "(this except ErrorA as that) except ErrorB as other" rather than "this except ErrorA as (that except ErrorB as other)" IMO.
I don't think it reads very well, though. The statement form (1) emphasizes the main operation compared to the "try", and (2) suggests that catching an exception is quite a heavy operation compared to execution without an exception, which is true.
Compared to the block try/except syntax, "as" would have to mean "bind the exception to this name before going into the exception-handling block", which is quite different from your proposal. I can mention it in the PEP if you like, though. ChrisA
Chris Angelico writes:
On Thu, Feb 13, 2014 at 3:57 PM, Stephen J. Turnbull
wrote: Why not spell it the same way as in a try statement?
response_text = request('http://whatever.com').text except HttpError as "can't access data"
Compared to the block try/except syntax, "as" would have to mean "bind the exception to this name before going into the exception-handling block", which is quite different from your proposal.
True.
I can mention it in the PEP if you like, though.
No, please don't. Your point kills it. My main thing is to avoid encouraging use of "may_raise() except ExcType as exc WHATEVER default_value". I don't think an expression should return multiple values through separate channels this way, and as posted elsewhere I think use of exc *in* the expression is "complicated".
On Sat, Feb 15, 2014 at 8:03 PM, Stephen J. Turnbull
My main thing is to avoid encouraging use of "may_raise() except ExcType as exc WHATEVER default_value". I don't think an expression should return multiple values through separate channels this way, and as posted elsewhere I think use of exc *in* the expression is "complicated".
Understood, but I can well imagine there'll be plenty of cases where it makes sense to fold "return value or exception message" into a single value, possibly including a tag of some sort. ret = urlopen(addr) except HTTPError as e pass "Oops - "+e.reason So it makes good sense to grab the exception and use it in the expression. Anyway, the PEP's written and submitted to the pep editors, so as soon as something's posted, the bikeshedding can begin anew :) ChrisA
While I'm generally positive about the idea of an except expression (and favor A except E pass B over any of the alternatives), I think it's worth looking in a different direction. One of the motivations of this idea is that there are some use cases that are particularly inconvenient. Another is the proliferation of APIs accepting default values. So instead of a general solution, let me suggest an alternative that addresses specific use cases. I'll write these in terms of an except expression so that they can be directly compared head to head. a[?b] <=> a[b] except (IndexError, TypeError)pass None a.?b <=> a.b except AttributeError pass None a(?b) <=> a(b) except Exception pass None a or? b <=> (a except Exception pass None) or b a and? b <=> (a except Exception pass None) and b del? a.b <=> try: del a.b except AtributeError: pass Of course you can chain these: a[?b][?c] a.?b.?c a or? b or? c Pros/Cons: - no option to specify replacement value on an exception - no option to specify exceptions to catch - doesn't specifically solve the api default value issue -- and the option for that case a(?b) catches all exceptions rather than specific ones --- Bruce Learn how hackers think: http://j.mp/gruyere-security Note: the syntax could just as easily be a?[b], a?.b, etc.
I don't think adding multiple elements of syntax for an incomplete solution
makes sense.
On Feb 15, 2014 4:09 PM, "Bruce Leban"
While I'm generally positive about the idea of an except expression (and favor A except E pass B over any of the alternatives), I think it's worth looking in a different direction.
One of the motivations of this idea is that there are some use cases that are particularly inconvenient. Another is the proliferation of APIs accepting default values.
So instead of a general solution, let me suggest an alternative that addresses specific use cases. I'll write these in terms of an except expression so that they can be directly compared head to head.
a[?b] <=> a[b] except (IndexError, TypeError)pass None a.?b <=> a.b except AttributeError pass None a(?b) <=> a(b) except Exception pass None a or? b <=> (a except Exception pass None) or b a and? b <=> (a except Exception pass None) and b
del? a.b <=> try: del a.b except AtributeError: pass
Of course you can chain these:
a[?b][?c] a.?b.?c
a or? b or? c
Pros/Cons: - no option to specify replacement value on an exception - no option to specify exceptions to catch - doesn't specifically solve the api default value issue -- and the option for that case a(?b) catches all exceptions rather than specific ones
--- Bruce Learn how hackers think: http://j.mp/gruyere-security
Note: the syntax could just as easily be a?[b], a?.b, etc.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
"Stephen J. Turnbull"
Ben Finney writes:
try: response_text = request('http://whatever.com').text except HttpError: "Can't access data"
This has incorrect semantics. The correct semantics would be
try: response_text = request('http://whatever.com').text except HttpError: response_text = "Can't access data"
I assume.
You assume correctly; I wrote the above code too hastily. Thanks for the correction. -- \ “Shepherds … look after their sheep so they can, first, fleece | `\ them and second, turn them into meat. That's much more like the | _o__) priesthood as I know it.” —Christopher Hitchens, 2008-10-29 | Ben Finney
On 02/12/2014 10:02 PM, 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.
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 :)
After some considerations, it seems we reached the point of generalising the idea to: * provide a builtin way to indicate a special value for the special cases where the standard value's expression would raise an exception As Nick Coghlan shows in another post, this is in fact close to a builtin way to deal with potentially failing functions, in general (below 'op'): def _helper(op, exc, make_default): try: return op() except exc: return make_default() x = _helper(op, Exception, make_default) However, this only applies to the case of functions properly speaking (which purpose is computing a product). What about 'actions', meaning procedures that perform an effect? Take the case of count-words for instance, where (in python) when encoutering a word one would add 1 to its count in a dict which keys are the words. On first encounter, there no entry yet for that word. We could check "word in word_counts", but this is doing the dict lookup twice; and catching an exception is worse. The problem I guess, here and also similarly for functions, is that there is no way for the _client_ (the caller) to control exception throwing; while only the caller knows whether the failing case actually is an error or not, meaning belongs or not the application's logics. Maybe what we need is in fact something like: maybe statement [then dependant-block] else alternative-block This is similar in form to exception catching except (sic!) that the actual meaning is to _avoid_ an exception, not to catch it (to avoid it beeing thrown at all). More or less the opposite, in fact. The consequence is that in case of failure no exception is raised, instead the alternative 'else' branch is taken. Such a construct would work fine for both called actions and functions (which calls are expressions in statement). [1] [Similar considerations are evoked in other languages, especially D where I first met them. Generally speaking, people start to realise the seamntic weakness of typical exception catching mecanisms which let no choice or control in the hands of the one who actually knows, namely the client.] d [1] Practically, this just means adding a test before throwing, and setting a flag instead (the carry would do the job nicely, since it can have no meaning on func return, and is the only copy flag one can set arbitrarily). Thus, the above code translates to: statement if flag: reset flag alternative-block [else: dependant-block]
On Feb 13, 2014, at 10:30, spir
On 02/12/2014 10:02 PM, 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.
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 :)
After some considerations, it seems we reached the point of generalising the idea to: * provide a builtin way to indicate a special value for the special cases where the standard value's expression would raise an exception As Nick Coghlan shows in another post, this is in fact close to a builtin way to deal with potentially failing functions, in general (below 'op'):
def _helper(op, exc, make_default): try: return op() except exc: return make_default()
x = _helper(op, Exception, make_default)
However, this only applies to the case of functions properly speaking (which purpose is computing a product). What about 'actions', meaning procedures that perform an effect? Take the case of count-words for instance, where (in python) when encoutering a word one would add 1 to its count in a dict which keys are the words. On first encounter, there no entry yet for that word. We could check "word in word_counts", but this is doing the dict lookup twice; and catching an exception is worse.
Or you could use setdefault, or use a defaultdict, or, better, a Counter. And I don't see how you expect to handle this situation with new syntax. You want to do word_counts[word] = 0 in the case where word_counts[word] raised--but you also want to "unfail" that word_counts[word] and provide a value for it to return. That can't be the value of word_counts[word] = 0, because statements don't have values. And, even if you could figure out how to provide the number 0, that wouldn't help anyway, because what you're looking for is an augmented assignment target, and those aren't even values in the language that can be passed around.
The problem I guess, here and also similarly for functions, is that there is no way for the _client_ (the caller) to control exception throwing; while only the caller knows whether the failing case actually is an error or not, meaning belongs or not the application's logics. Maybe what we need is in fact something like:
maybe statement [then dependant-block] else alternative-block
This is similar in form to exception catching except (sic!) that the actual meaning is to _avoid_ an exception, not to catch it (to avoid it beeing thrown at all).
How do you avoid an exception being thrown? What happens if some function called inside statement tries to raise? If the answer is "we immediately abort that function, and the rest of the statement, and run the alternative block, then what you've described is just exception handling, as it already exists: try: statement except: alternative block else: dependent block All you've done is make it less powerful--you can't limit it to specific types, use the value of the exception, try a complex statement, etc. And I can't think of any other sensible think you could mean here.
More or less the opposite, in fact. The consequence is that in case of failure no exception is raised, instead the alternative 'else' branch is taken.
Are you just looking to use a flag to shortcut the creation and propagation of an exception object here? If not, in what other way is this semantically different from "an exception is raised, and handled by the alternative branch"?
Such a construct would work fine for both called actions and functions (which calls are expressions in statement). [1]
[Similar considerations are evoked in other languages, especially D where I first met them. Generally speaking, people start to realise the seamntic weakness of typical exception catching mecanisms which let no choice or control in the hands of the one who actually knows, namely the client.]
d
[1] Practically, this just means adding a test before throwing, and setting a flag instead (the carry would do the job nicely, since it can have no meaning on func return, and is the only copy flag one can set arbitrarily).
And then continuing to execute the rest of the function? Or constructing and returning some value nobody will ever look at? And what happens if the exception is raised in a function three levels down the stack? How are you going to abort all three of them? Really, the only thing you can do when the function tries to raise is to abort the whole function, and all the way up to some piece of code that's looking to deal with the problem. You need a flag that's checked in multiple places in the interpreter to make sure it's propagated correctly. Which is exactly what raise already does. So you'd just be providing a different kind of exception--one that can't be caught by try/except, but can be caught by maybe. But if you want two kinds of exception, why not many kinds, which any hierarchy you want--which you can already do today, because exception types are classes.
Thus, the above code translates to: statement if flag: reset flag alternative-block [else: dependant-block]
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
I see two motivating factors for an except expression: - one line try/except handling for simple cases - easier default value handling when function/method does not support a default value Given that, I don't think we need to support multiple exception types or chained exceptions. If it's that complicated, use an actual try block -- it'll be much more readable. -- ~Ethan~
On Sun, Feb 16, 2014 at 07:31:17PM -0800, Ethan Furman wrote:
I see two motivating factors for an except expression:
- one line try/except handling for simple cases - easier default value handling when function/method does not support a default value
Given that, I don't think we need to support multiple exception types or chained exceptions. If it's that complicated, use an actual try block -- it'll be much more readable.
I'm sympathetic to this view, but I think that by using appropriate parentheses, multiple exception types and multiple exceptions are very readable. Of course, like deeply nested list comps or generator expressions, or too many ternary-if expressions, you can obfuscate your code by abusing this proposed syntax. The answer to that is, Don't Do That. -- Steven
The three use-cases I find compelling are: # long form try: result = 1/x except ZeroDivisionError: result = NaN # previously defined try: os.unlink(some_file) except OSError: pass try: result = some_func(value1, value2) except SomeError: result = 42 which would be converted to (using Nick's notation): result = 1/x except ZeroDivisionError -> NaN os.unlink(some_file) except OSError -> None result = some_func(value1, value2) except SomeError -> 42 Clean, clear, concise. For the record, I could just as easily live with the colon instead of the arrow. -- ~Ethan~
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
which would be converted to (using Nick's notation):
result = 1/x except ZeroDivisionError -> NaN
result = some_func(value1, value2) except SomeError -> 42
These two I strongly support (colon or arrow, either way).
os.unlink(some_file) except OSError -> None
This one, not so much. You're using os.unlink(some_file) as a statement, ignoring its return value. Changing its return value to None in the case of an exception isn't exactly what you're trying to do. Yes, it does make for a one-liner, where the full form takes two: try: os.unlink(some_file) except OSError: pass but I'm not sure that it's the right way to do things. ChrisA
On 02/19/2014 05:00 PM, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: which would be converted to (using Nick's notation):
result = 1/x except ZeroDivisionError -> NaN
result = some_func(value1, value2) except SomeError -> 42
These two I strongly support (colon or arrow, either way).
os.unlink(some_file) except OSError -> None
This one, not so much. You're using os.unlink(some_file) as a statement, ignoring its return value. Changing its return value to None in the case of an exception isn't exactly what you're trying to do. Yes, it does make for a one-liner, where the full form takes two:
try: os.unlink(some_file) except OSError: pass
but I'm not sure that it's the right way to do things.
1) It could be used inside a function call or assigned to a variable; 2) We're creating a new expression type -- it'll be valid anywhere an expression is valid, even all by itself. e.g. `1 + 2` is valid, even though nothing is done with the result. -- ~Ethan~
From: Ethan Furman
Subject: Re: [Python-ideas] except expression
On 02/19/2014 05:00 PM, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: which would be converted to (using Nick's notation):
result = 1/x except ZeroDivisionError -> NaN
result = some_func(value1, value2) except SomeError -> 42
These two I strongly support (colon or arrow, either way).
os.unlink(some_file) except OSError -> None
This one, not so much. You're using os.unlink(some_file) as a statement, ignoring its return value. Changing its return value to None in the case of an exception isn't exactly what you're trying to do. Yes, it does make for a one-liner, where the full form takes two:
try: os.unlink(some_file) except OSError: pass
but I'm not sure that it's the right way to do things.
1) It could be used inside a function call or assigned to a variable;
Why would you use an expression that always evaluates to None (with or without an except handler that also always evaluates to None) in a function call or assignment, or really anywhere else but a simple expression statement?
2) We're creating a new expression type -- it'll be valid anywhere an expression is valid, even all by itself. e.g. `1 + 2` is valid, even though nothing is done with the result.
Sure, it's valid, but it's also useless (except in the interactive interpreter, where something _is_ done with the result), so we don't need to add syntax to make it more powerful. Twice as powerful as useless is still useless. So, while there's no need to actually ban "os.unlink(some_file) except OSError: None", it doesn't make a good argument for the PEP.
On Wed, Feb 19, 2014 at 05:34:26PM -0800, Andrew Barnert wrote:
So, while there's no need to actually ban "os.unlink(some_file) except OSError: None", it doesn't make a good argument for the PEP.
What he said. It's actually a bad argument for the PEP, as it's a misuse of the expression form. A slightly less-worse example might be: errors = filter(bool, [os.unlink(file) except OSError as err: err.args[0] for file in files]) except we're deferring the "as err" part :-) -- Steven
On Thu, Feb 20, 2014 at 12:05 PM, Ethan Furman
On 02/19/2014 05:00 PM, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: os.unlink(some_file) except OSError -> None
Yes, it does make for a one-liner, where the full form takes two:
try: os.unlink(some_file) except OSError: pass
but I'm not sure that it's the right way to do things.
1) It could be used inside a function call or assigned to a variable;
If you're using the return value, then sure. That's exactly what this is for. But as far as I know, os.unlink only ever returns None, so saying "and if it raises OSError, return None instead" doesn't make a lot of sense.
2) We're creating a new expression type -- it'll be valid anywhere an expression is valid, even all by itself. e.g. `1 + 2` is valid, even though nothing is done with the result.
Oh yes, I'm not going to *prevent* this sort of thing. It's no different from reworking your import statements into __import__ calls just so you can use ternary if to handle versions: tkinter = __import__("Tkinter" if sys.version_info.major==2 else "tkinter") Is it good code? I don't think so. Does anything stop you from doing it? Of course not. And if you really wanted to, you could probably turn your entire Python program into a single megantic (that's like gigantic only not so much - next one down is kilantic, and below that you just have antics - see also http://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid=370794) lambda, make everything into expressions, and do all assignments by declaring functions and calling them. But it wouldn't be Pythonic code. ChrisA
On 20 February 2014 10:15, Ethan Furman
try: os.unlink(some_file) except OSError: pass
Note that Python 3.4+ (and previous versions once I get around to doing a new contextlib2 release) allows the statement case to be written as the one-liner: with suppress(OSError): os.unlink(some_file) I don't think this PEP needs to worry about that case, but Chris may want to explicitly reference contextlib.suppress as being preferred to "os.unlink(some_file) except OSError -> None" (or 0 or ... or whatever other dummy placeholder someone decided to use). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Thu, Feb 20, 2014 at 12:09 PM, Nick Coghlan
On 20 February 2014 10:15, Ethan Furman
wrote: try: os.unlink(some_file) except OSError: pass
Note that Python 3.4+ (and previous versions once I get around to doing a new contextlib2 release) allows the statement case to be written as the one-liner:
with suppress(OSError): os.unlink(some_file)
I don't think this PEP needs to worry about that case, but Chris may want to explicitly reference contextlib.suppress as being preferred to "os.unlink(some_file) except OSError -> None" (or 0 or ... or whatever other dummy placeholder someone decided to use).
Thank you, yes. I'll add that message and move the whole idea to the rejected section. ChrisA
On 02/19/2014 05:25 PM, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 12:09 PM, Nick Coghlan
wrote: On 20 February 2014 10:15, Ethan Furman
wrote: try: os.unlink(some_file) except OSError: pass
Note that Python 3.4+ (and previous versions once I get around to doing a new contextlib2 release) allows the statement case to be written as the one-liner:
with suppress(OSError): os.unlink(some_file)
I don't think this PEP needs to worry about that case, but Chris may want to explicitly reference contextlib.suppress as being preferred to "os.unlink(some_file) except OSError -> None" (or 0 or ... or whatever other dummy placeholder someone decided to use).
Thank you, yes. I'll add that message and move the whole idea to the rejected section.
Careful how you word that. "Rejected" makes it sound like it won't work, not that it will but is not the best way. -- ~Ethan~
Ethan Furman writes:
Thank you, yes. I'll add that message and move the whole idea to the rejected section.
Careful how you word that. "Rejected" makes it sound like it won't work, not that it will but is not the best way.
Nope. "Rejected" means a decision was made. It doesn't imply anything about the reason for the decision. If you think it does, that's your problem. Chris's usage was perfectly fine in the context of python-dev practice.
On Thu, Feb 20, 2014 at 2:13 PM, Stephen J. Turnbull
Ethan Furman writes:
Thank you, yes. I'll add that message and move the whole idea to the rejected section.
Careful how you word that. "Rejected" makes it sound like it won't work, not that it will but is not the best way.
Nope. "Rejected" means a decision was made. It doesn't imply anything about the reason for the decision. If you think it does, that's your problem. Chris's usage was perfectly fine in the context of python-dev practice.
And by the way, reasons for decisions are often going into the PEP itself; when I make a quick comment on list about a rejection, I won't repeat everything that I've said there, so you'd need to check the draft PEP for the reasoning. ChrisA
On Thu, Feb 20, 2014 at 1:29 PM, Ethan Furman
Thank you, yes. I'll add that message and move the whole idea to the rejected section.
Careful how you word that. "Rejected" makes it sound like it won't work, not that it will but is not the best way.
What I'm rejecting is the proposal for: statement except Expression: pass Based on the current proposal, that won't work. It makes reasonable sense, but we can't do everything. As Rob says, that may be better done separately; it's really an alternative short-hand for the statement form, not an expression form. This will work, but is not recommended: expression except Expression: None That's not part of the rejected section. ChrisA
On 20/02/2014 01:09, Nick Coghlan wrote:
On 20 February 2014 10:15, Ethan Furman
wrote: try: os.unlink(some_file) except OSError: pass Note that Python 3.4+ (and previous versions once I get around to doing a new contextlib2 release) allows the statement case to be written as the one-liner:
with suppress(OSError): os.unlink(some_file)
I don't think this PEP needs to worry about that case, but Chris may want to explicitly reference contextlib.suppress as being preferred to "os.unlink(some_file) except OSError -> None" (or 0 or ... or whatever other dummy placeholder someone decided to use).
Cheers, Nick.
Can I put in another plug for allowing: os.unlink(some_file) except OSError: pass Cons: It is anomalous. It only makes sense in an expression whose value is not used. Pros: It is very readable. It is a common use case. But maybe it should go, if anywhere, in a separate PEP. Thanks, Rob Cliffe
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow? For the purposes of this debate, I'm comparing these two notations, and nothing else: result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN Advantages of the colon include: * It's already in use in most programs (how many Python programmers even know about the arrow?) * Parallel with the way lambda uses a colon to separate its arg list from a sub-expression * Parallel with statement form of "except X:" * Compactness (one character and a space to its right, vs two and spaces both sides) - a good thing, as this notation tends to want to be on one line Advantages of the arrow include -> * Splits nicely at the arrow: "1/x except ZeroDivisionError\n\t-> NaN" - doing this with a colon would look like introducing a suite, which would be extremely confusing * Unique syntax - can't be confused with starting a suite or initializing a dict * Comparable amount of visual separation between the three parts. Like with "if/else", where there's a whole word separating each part, the arrow gives a decent separator between the exception and the default. The colon binds the two closely together. * Distinguishes itself from the statement "except X:", which introduces more statements. Both proposals fit into my preferred layout order (meaning the evaluation order is strictly left to right). Neither adds new keywords to the language. Both put the word "except" immediately ahead of the exception list. Either would be plausible. ChrisA
From: Chris Angelico
Subject: Re: [Python-ideas] except expression
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN
I hated the colon at first, but after seeing it in more simple cases like this one, I'm starting to warm to it. And I definitely like it better than the arrow. For reasons you mostly already elaborated:
Advantages of the colon include:
* It's already in use in most programs (how many Python programmers even know about the arrow?)
Yes. The colon "looks more like Python" here—maybe only because you don't see function annotations nearly as often as compound statements, lambda expressions, dict displays, etc., but that's still compelling. Also, I've never really liked -> anywhere, but that may just be because I first used that in ML, and we were using a textbook that used -> but an implementation that used =>, so feel free to discount that too.
* Parallel with the way lambda uses a colon to separate its arg list from a sub-expression
I'm not sure how much this is worth. It loomed big in my head right after trying to write the most pythonic except-expression in Haskell and then translating it to Python. But that's equivalent to the fact that if you wanted to do this with a function instead of syntax you'd have to use a lambda, which is just as true of the if expression or a short-circuiting or or and expression, which don't need colons.
* Parallel with statement form of "except X:"
I think this is the one that sells it to me.
* Compactness (one character and a space to its right, vs two and spaces both sides) - a good thing, as this notation tends to want to be on one line
Advantages of the arrow include -> * Splits nicely at the arrow: "1/x except ZeroDivisionError\n\t-> NaN" - doing this with a colon would look like introducing a suite, which would be extremely confusing * Unique syntax - can't be confused with starting a suite or initializing a dict * Comparable amount of visual separation between the three parts. Like with "if/else", where there's a whole word separating each part, the arrow gives a decent separator between the exception and the default. The colon binds the two closely together. * Distinguishes itself from the statement "except X:", which introduces more statements.
Both proposals fit into my preferred layout order (meaning the evaluation order is strictly left to right). Neither adds new keywords to the language. Both put the word "except" immediately ahead of the exception list. Either would be plausible.
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 2/19/2014, 8:18 PM, Chris Angelico wrote:
Advantages of the arrow include -> I would refrain from introducing a new operator here, especially '->'.
This one may later be used for communication between channels/queues or other async stuff (like in golang, although they use '<-'.) Yury
On Thu, Feb 20, 2014 at 2:55 PM, Yury Selivanov
On 2/19/2014, 8:18 PM, Chris Angelico wrote:
Advantages of the arrow include ->
I would refrain from introducing a new operator here, especially '->'.
This one may later be used for communication between channels/queues or other async stuff (like in golang, although they use '<-'.)
Technically, -> does exist in the language: http://www.python.org/dev/peps/pep-3107/ I don't know if it's still available for use as an operator, but I'd be extremely cautious about using <- as an operator; it could be binary less-than followed by unary minus:
1 <- 2 False
Demanding space around binary operators is the job of style guides, but creating <- as an operator would mean first moving that demand into the language (at least in some places). ChrisA
On 2/19/2014, 11:24 PM, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 2:55 PM, Yury Selivanov
wrote: On 2/19/2014, 8:18 PM, Chris Angelico wrote:
Advantages of the arrow include -> I would refrain from introducing a new operator here, especially '->'.
This one may later be used for communication between channels/queues or other async stuff (like in golang, although they use '<-'.) Technically, -> does exist in the language: I know, and this is another reason to not to use '->' for the except expression.
http://www.python.org/dev/peps/pep-3107/
I don't know if it's still available for use as an operator, but I'd be extremely cautious about using <- as an operator; it could be binary less-than followed by unary minus:
Sorry, I can't find where I suggested to use '<-' in Python. Yury
On Thu, Feb 20, 2014 at 3:37 PM, Yury Selivanov
On 2/19/2014, 11:24 PM, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 2:55 PM, Yury Selivanov
wrote: On 2/19/2014, 8:18 PM, Chris Angelico wrote:
Advantages of the arrow include ->
I would refrain from introducing a new operator here, especially '->'.
This one may later be used for communication between channels/queues or other async stuff (like in golang, although they use '<-'.)
Technically, -> does exist in the language:
I know, and this is another reason to not to use '->' for the except expression.
I'd call that a medium-weak negative. If there were a current competing proposal, then that would be a strong incentive to leave that character stream alone. Reserving it for a hypothetical future proposal is only a weak objection. It's unlikely that this would warp any other protocol excessively, but it's impossible to warp this one around a nebulous "maybe". It is a small argument in favour of the colon, though, simply because that doesn't have this problem. But a small argument.
I don't know if it's still available for use as an operator, but I'd be extremely cautious about using <- as an operator; it could be binary less-than followed by unary minus:
Sorry, I can't find where I suggested to use '<-' in Python.
If -> got snaffled by exception handling, then async comms could just say "oh, let's use the other then". But it would be problematic to. ChrisA
On Wed, Feb 19, 2014 at 10:55:58PM -0500, Yury Selivanov wrote:
On 2/19/2014, 8:18 PM, Chris Angelico wrote:
Advantages of the arrow include -> I would refrain from introducing a new operator here, especially '->'.
It's not new. It's used in annotations: py> def func(a, b) -> "Return result": ... return "something" ... py> func.__annotations__ {'return': 'Return result'} -- Steven
While I'm growing to like the '->' operator, at least better than the colon
that just looks wrong to me; I would defend Chris' characterization of it
as "new." That is, the symbols exist as function annotations already, but
the current use is not as an *operator*, so that's technically true.
On Thu, Feb 20, 2014 at 4:10 PM, Steven D'Aprano
On Wed, Feb 19, 2014 at 10:55:58PM -0500, Yury Selivanov wrote:
On 2/19/2014, 8:18 PM, Chris Angelico wrote:
Advantages of the arrow include -> I would refrain from introducing a new operator here, especially '->'.
It's not new. It's used in annotations:
py> def func(a, b) -> "Return result": ... return "something" ... py> func.__annotations__ {'return': 'Return result'}
-- 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/
-- 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.
On 2014-02-20 01:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
[snip] I definitely prefer the colon.
On 20/02/2014 01:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow? Colon, please. More compact, reads naturally as it's part of the English language, consistent with "except" statements. Rob Cliffe.
On 20.02.2014 02:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN
I'm -1 on both of them. The colon should stay reserved for starting new blocks of statements. The arrow is used for return type annotations, which is a completely different concept than returning values. I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions. The intended use case should really be limited to providing default values for functions or methods that are expected to return a value. Abusing the fact that procedures in Python return None (simply because we don't have procedures and need to use functions instead) to make use of except expressions would make code less readable. x = data[1] except IndexError return None # is readable f = open('x.txt', 'r') except IOError return None # is probably not a good idea os.remove('/') except IOError return None # is really bad style The purpose of such except expressions should be to work around small corner cases, not to address important exceptional cases in ones applications. Sometimes I wish we had expression objects in Python to wrap expressions without evaluating them - sort of like lambdas without arguments but with a nicer syntax. These could then be used to implement a default() builtin. Here's a version using lambdas as example: def default(expr, value=None, *exceptions): try: expr() except exceptions: return value x = default(lambda: 1/0, None, ZeroDivisionError) print (x) -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Feb 20 2014)
Python Projects, Consulting and Support ... http://www.egenix.com/ mxODBC.Zope/Plone.Database.Adapter ... http://zope.egenix.com/ mxODBC, mxDateTime, mxTextTools ... http://python.egenix.com/
2014-02-12: Released mxODBC.Connect 2.0.4 ... http://egenix.com/go53 ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/
On 20.02.2014 02:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN I'm -1 on both of them. I'm afraid answering your post will mean repeating stuff said earlier in
The colon should stay reserved for starting new blocks of statements. It isn't reserved for that - it is already used for slices, dictionary
On 20/02/2014 13:45, M.-A. Lemburg wrote: this thread, but here goes: literals and lambdas.
The arrow is used for return type annotations, which is a completely different concept than returning values. (I'm not so keen on the arrow myself but) with a limited set of characters, some of them have to perform more than one role, as colons, dots, parentheses and braces (to name but a few) already do.
I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions. This is a relatively common construction (as a survey of my own small codebase indicates). Quick, yes. How dirty it is depends entirely on context, (and how readable it can be made) and is up to the programmer's judgment.
The intended use case should really be limited to providing default values for functions or methods that are expected to return a value. That's your opinion. Why, if it is useful in other ways? [1]
Abusing the fact that procedures in Python return None (simply because we don't have procedures and need to use functions instead) to make use of except expressions would make code less readable. I don't quite see the relevance. None of your examples rely on a function returning None. Could you give an example?
x = data[1] except IndexError return None # is readable Agreed. f = open('x.txt', 'r') except IOError return None # is probably not a good idea I personally think this could be spelt more readably (with a colon or "then"). But however it's done, it's a meaningful and useful construct. os.remove('/') except IOError return None # is really bad style Do you mean because it's not very readable (I agree) or because it's necessarily a bad thing to do (I disagree, particularly if we chose a less drastic example)?
The purpose of such except expressions should be to work around small corner cases, not to address important exceptional cases in ones applications. See [1] above.
Sometimes I wish we had expression objects in Python to wrap expressions without evaluating them - sort of like lambdas without arguments but with a nicer syntax. These could then be used to implement a default() builtin.
Here's a version using lambdas as example:
def default(expr, value=None, *exceptions): try: expr() except exceptions: return value
x = default(lambda: 1/0, None, ZeroDivisionError)
print (x)
This is interesting. Expression objects might be useful in various ways. But it seems a rather circuitous and obscure way to provide a default value after an exception, and harder to extend: x = default(lambda: d1[key], default(lambda:d2[key], None, KeyError), KeyError) # And I hope I've spelt it right! Rob Cliffe
On 20.02.2014 15:46, Rob Cliffe wrote:
On 20.02.2014 02:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN I'm -1 on both of them. I'm afraid answering your post will mean repeating stuff said earlier in this thread, but here goes: The colon should stay reserved for starting new blocks of statements. It isn't reserved for that - it is already used for slices, dictionary literals and lambdas. The arrow is used for return type annotations, which is a completely different concept than returning values. (I'm not so keen on the arrow myself but) with a limited set of characters, some of them have to
On 20/02/2014 13:45, M.-A. Lemburg wrote: perform more than one role, as colons, dots, parentheses and braces (to name but a few) already do.
I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions.
This is a relatively common construction (as a survey of my own small codebase indicates). Quick, yes. How dirty it is depends entirely on context, (and how readable it can be made) and is up to the programmer's judgment.
The intended use case should really be limited to providing default values for functions or methods that are expected to return a value.
That's your opinion. Why, if it is useful in other ways? [1]
Sure, it's my opinion :-) That's what this mailing list is all about: tossing around ideas. As I've mentioned before, I think people are putting too many features into this expression style and just want to express this concern. The thread started out with the objective to find a solution for the common default argument problem - which is a good thing. The current discussion is getting somewhat out of focus.
Abusing the fact that procedures in Python return None (simply because we don't have procedures and need to use functions instead) to make use of except expressions would make code less readable. I don't quite see the relevance. None of your examples rely on a function returning None. Could you give an example?
I gave an example below and Nick gave another one.
x = data[1] except IndexError return None # is readable Agreed. f = open('x.txt', 'r') except IOError return None # is probably not a good idea I personally think this could be spelt more readably (with a colon or "then"). But however it's done, it's a meaningful and useful construct.
I usually consider an IOError to be too serious to hide away in a single line.
os.remove('/') except IOError return None # is really bad style Do you mean because it's not very readable (I agree) or because it's necessarily a bad thing to do (I disagree, particularly if we chose a less drastic example)?
The above is the procedure example I was talking about above. I find this an even worse style than the open() error, since there's absolutely no need to use an expression for this - the os.remove() will never return a value, so you don't need an expression.
The purpose of such except expressions should be to work around small corner cases, not to address important exceptional cases in ones applications. See [1] above.
Sometimes I wish we had expression objects in Python to wrap expressions without evaluating them - sort of like lambdas without arguments but with a nicer syntax. These could then be used to implement a default() builtin.
Here's a version using lambdas as example:
def default(expr, value=None, *exceptions): try: expr() except exceptions: return value
x = default(lambda: 1/0, None, ZeroDivisionError)
print (x)
This is interesting. Expression objects might be useful in various ways. But it seems a rather circuitous and obscure way to provide a default value after an exception, and harder to extend: x = default(lambda: d1[key], default(lambda:d2[key], None, KeyError), KeyError) # And I hope I've spelt it right!
Well, like I said: the lambda notation doesn't make this look very nice, but the builtin function approach certainly is a lot more flexible than having to introduce a new syntax. BTW: The indexing example that started this thread is so common that I put a special function into mxTools to address it (back in 1998): get(object,index[,default]) Returns object[index], or, if that fails, default. and this works for all indexing compatible objects. You can find more such tools here: http://www.egenix.com/products/python/mxBase/mxTools/ (a lot of those things are no longer needed, since Python has grown built-in support for them over the years) -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Feb 20 2014)
Python Projects, Consulting and Support ... http://www.egenix.com/ mxODBC.Zope/Plone.Database.Adapter ... http://zope.egenix.com/ mxODBC, mxDateTime, mxTextTools ... http://python.egenix.com/
2014-02-12: Released mxODBC.Connect 2.0.4 ... http://egenix.com/go53 ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/
On Fri, Feb 21, 2014 at 2:22 AM, M.-A. Lemburg
os.remove('/') except IOError return None # is really bad style Do you mean because it's not very readable (I agree) or because it's necessarily a bad thing to do (I disagree, particularly if we chose a less drastic example)?
The above is the procedure example I was talking about above.
I find this an even worse style than the open() error, since there's absolutely no need to use an expression for this - the os.remove() will never return a value, so you don't need an expression.
And that's why I, too, decry this as a bad use of the feature. It's like writing: os.remove(fn) or None which implies (a) that os.remove() might return something other than None, and (b) that the value of the expression is important. Both implications mislead the reader. The insertion of a single line break will do it. Let it stand that: try: os.remove(fn) except OSError: pass and there you are, out of your difficulty at once! (Or, as mentioned, contextlib.suppress.) ChrisA
On Thu, Feb 20, 2014 at 9:46 AM, Rob Cliffe
I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions.
This is a relatively common construction (as a survey of my own small codebase indicates). Quick, yes. How dirty it is depends entirely on context, (and how readable it can be made) and is up to the programmer's judgment.
Isn't it what contextlib.suppress() [1] was invented for? Do we need yet another way to express the same? http://docs.python.org/3.4/library/contextlib.html#contextlib.suppress
On 20/02/2014 15:23, Alexander Belopolsky wrote:
On Thu, Feb 20, 2014 at 9:46 AM, Rob Cliffe
mailto:rob.cliffe@btinternet.com> wrote: I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions.
This is a relatively common construction (as a survey of my own small codebase indicates). Quick, yes. How dirty it is depends entirely on context, (and how readable it can be made) and is up to the programmer's judgment.
Isn't it what contextlib.suppress() [1] was invented for? Do we need yet another way to express the same?
Well maybe. I have never learned about context managers and know nothing about them. And without some other incentive, rather than make the effort, I will stick with the tried and trusted "try ... except ... finally" workhorse which is built into the language and which I do understand.
http://docs.python.org/3.4/library/contextlib.html#contextlib.suppress
No virus found in this message. Checked by AVG - www.avg.com http://www.avg.com Version: 2012.0.2247 / Virus Database: 3705/6609 - Release Date: 02/20/14
On 2014-02-20 14:46, Rob Cliffe wrote:
On 20.02.2014 02:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN I'm -1 on both of them. I'm afraid answering your post will mean repeating stuff said earlier in
The colon should stay reserved for starting new blocks of statements. It isn't reserved for that - it is already used for slices, dictionary
On 20/02/2014 13:45, M.-A. Lemburg wrote: this thread, but here goes: literals and lambdas.
It should be said that slices and dict literals do "enclose" the colon, the first with in[...] and the second within {...}. @Marc-Andre: Would it be better if the expression containing the except clause were enclosed in (...), e.g. "result = (1/x except ZeroDivisionError: NaN)" rather than "result = 1/x except ZeroDivisionError: NaN"? [snip]
20.02.2014 16:24, MRAB wrote:
On 2014-02-20 14:46, Rob Cliffe wrote:
On 20.02.2014 02:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN I'm -1 on both of them. I'm afraid answering your post will mean repeating stuff said earlier in
The colon should stay reserved for starting new blocks of statements. It isn't reserved for that - it is already used for slices, dictionary
On 20/02/2014 13:45, M.-A. Lemburg wrote: this thread, but here goes: literals and lambdas.
It should be said that slices and dict literals do "enclose" the colon, the first with in[...] and the second within {...}.
@Marc-Andre: Would it be better if the expression containing the except clause were enclosed in (...), e.g. "result = (1/x except ZeroDivisionError: NaN)" rather than "result = 1/x except ZeroDivisionError: NaN"?
+1 Cheers. *j
On Feb 20, 2014, at 5:45, "M.-A. Lemburg"
On 20.02.2014 02:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN
I'm -1 on both of them.
The colon should stay reserved for starting new blocks of statements.
So you don't like the colon in lambdas, dict displays, or slices? [snip]
I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions.
Agreed. Especially after Nick Coghlan demonstrated that we already have a more readable and more concise way to do it without abusing anything: with suppress(IOError): os.remove('/') Who sees that and says, "I like that it's one line, but if only it could be a longer line, with more keywords, and misleadingly imply a useful value"? [snip]
Sometimes I wish we had expression objects in Python to wrap expressions without evaluating them - sort of like lambdas without arguments but with a nicer syntax. These could then be used to implement a default() builtin.
There's no "sort of" about it; you want lambda with a nicer syntax. I sympathize with that. Why do you think Haskell gets away with catch being a function when other functional languages don't? Maybe it's this: catch \-> expensive_call \-e> (dangerous_default e) vs. this: catch(function() { expensive_call() }, function(e) { dangerous_default(e) } That being said, I don't know that it would have the same benefits in Python. In a language that already encourages defining functions all over the place like JavaScript or OCaml, the verbose syntax is painful. But Python isn't like that. Haskell also has a one-character compose operator, which I love in Haskell and would love in JS, but I don't miss it in Python. (We don't even have a compose function in the stdlib.) Python lets you use functional style when it's the obvious way to express something, but doesn't force it on you when it isn't.
I too am uncomfortable with re-purposing except ...: from introducing
a block to introducing either a block or an expression. "Keyword then
colon" is a great indicator for when you should start a new indent,
especially with regards to learners. Lambda undermines this enough as
it is, and dicts/slices don't have a keyword preceding them.
I think we aren't going deep enough into the rabbit hole. Why restrain
ourselves to trying to cram try-except-finally into an expression,
losing the finally part, when we already have a tool right under our
eyes, as Nick Coghlan pointed out, which leverages try-except-finally
entirely in a concise fashion, the with statement.
If context managers are amended to support a return value(maybe even
transforming one?), a with-expression could look like:
last = L.pop() with default(None, IndexError)
x = expr with produce_default(expensive_op, AnException)
contents = f.read() with open('filename') as f
d = Decimal(1) / Decimal(7) with Context(prec=5)
The syntax is cleaner, including when easing the original problem, and
is capable of so much more. I seem to be lacking modesty today,
because it seems like this is worth scrapping except-expression for
it.
(Sorry for the incorrect placement of my reply, I just subscribed.)
On 20 February 2014 15:49, Andrew Barnert
On Feb 20, 2014, at 5:45, "M.-A. Lemburg"
wrote: On 20.02.2014 02:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN
I'm -1 on both of them.
The colon should stay reserved for starting new blocks of statements.
So you don't like the colon in lambdas, dict displays, or slices?
[snip]
I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions.
Agreed. Especially after Nick Coghlan demonstrated that we already have a more readable and more concise way to do it without abusing anything:
with suppress(IOError): os.remove('/')
Who sees that and says, "I like that it's one line, but if only it could be a longer line, with more keywords, and misleadingly imply a useful value"?
[snip]
Sometimes I wish we had expression objects in Python to wrap expressions without evaluating them - sort of like lambdas without arguments but with a nicer syntax. These could then be used to implement a default() builtin.
There's no "sort of" about it; you want lambda with a nicer syntax.
I sympathize with that. Why do you think Haskell gets away with catch being a function when other functional languages don't? Maybe it's this:
catch \-> expensive_call \-e> (dangerous_default e)
vs. this:
catch(function() { expensive_call() }, function(e) { dangerous_default(e) }
That being said, I don't know that it would have the same benefits in Python. In a language that already encourages defining functions all over the place like JavaScript or OCaml, the verbose syntax is painful. But Python isn't like that. Haskell also has a one-character compose operator, which I love in Haskell and would love in JS, but I don't miss it in Python. (We don't even have a compose function in the stdlib.) Python lets you use functional style when it's the obvious way to express something, but doesn't force it on you when it isn't. _______________________________________________ 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 20, 2014, at 7:24, Yann Kaiser
I too am uncomfortable with re-purposing except ...: from introducing a block to introducing either a block or an expression. "Keyword then colon" is a great indicator for when you should start a new indent, especially with regards to learners. Lambda undermines this enough as it is, and dicts/slices don't have a keyword preceding them.
I think we aren't going deep enough into the rabbit hole. Why restrain ourselves to trying to cram try-except-finally into an expression, losing the finally part, when we already have a tool right under our eyes, as Nick Coghlan pointed out, which leverages try-except-finally entirely in a concise fashion, the with statement.
If context managers are amended to support a return value(maybe even transforming one?), a with-expression could look like:
This is a really interesting idea. Or maybe two connected but somewhat separate ones. Either way, I think this is worth fleshing out and making a new post out of. I suspect a lot of people will miss it buried in the middle of this gigantic thread.
last = L.pop() with default(None, IndexError) x = expr with produce_default(expensive_op, AnException) contents = f.read() with open('filename') as f d = Decimal(1) / Decimal(7) with Context(prec=5)
The syntax is cleaner, including when easing the original problem, and is capable of so much more. I seem to be lacking modesty today, because it seems like this is worth scrapping except-expression for it.
(Sorry for the incorrect placement of my reply, I just subscribed.)
On 20 February 2014 15:49, Andrew Barnert
wrote: On Feb 20, 2014, at 5:45, "M.-A. Lemburg"
wrote: On 20.02.2014 02:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN
I'm -1 on both of them.
The colon should stay reserved for starting new blocks of statements.
So you don't like the colon in lambdas, dict displays, or slices?
[snip]
I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions.
Agreed. Especially after Nick Coghlan demonstrated that we already have a more readable and more concise way to do it without abusing anything:
with suppress(IOError): os.remove('/')
Who sees that and says, "I like that it's one line, but if only it could be a longer line, with more keywords, and misleadingly imply a useful value"?
[snip]
Sometimes I wish we had expression objects in Python to wrap expressions without evaluating them - sort of like lambdas without arguments but with a nicer syntax. These could then be used to implement a default() builtin.
There's no "sort of" about it; you want lambda with a nicer syntax.
I sympathize with that. Why do you think Haskell gets away with catch being a function when other functional languages don't? Maybe it's this:
catch \-> expensive_call \-e> (dangerous_default e)
vs. this:
catch(function() { expensive_call() }, function(e) { dangerous_default(e) }
That being said, I don't know that it would have the same benefits in Python. In a language that already encourages defining functions all over the place like JavaScript or OCaml, the verbose syntax is painful. But Python isn't like that. Haskell also has a one-character compose operator, which I love in Haskell and would love in JS, but I don't miss it in Python. (We don't even have a compose function in the stdlib.) Python lets you use functional style when it's the obvious way to express something, but doesn't force it on you when it isn't. _______________________________________________ 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 Fri, Feb 21, 2014 at 3:05 AM, Andrew Barnert
I suspect a lot of people will miss it buried in the middle of this gigantic thread.
It is, a bit :) Gmail breaks the thread automatically when it hits 100 emails... and it's done that four times so far (in the space of a week, mark you, so that's 60 emails a day average). I'd like to get back to the topic at hand, if we can, so I'll ask this question again. Which do you prefer, out of these? value = expr except Exception -> default value = expr except Exception: default Reasons are preferred over mere preferences, but the latter have value too. And hating both is acceptable; so far, none of the keyword-only options has snowballed in support, but "expr except Exception pass default" and "expr except Exception then default" are probably the front runners in the "no new keywords" and "one new keyword" categories. ChrisA
On Fri, Feb 21, 2014 at 2:24 AM, Yann Kaiser
I too am uncomfortable with re-purposing except ...: from introducing a block to introducing either a block or an expression. "Keyword then colon" is a great indicator for when you should start a new indent, especially with regards to learners. Lambda undermines this enough as it is, and dicts/slices don't have a keyword preceding them.
"Keyword then colon at the end of a line" is still a good indicator for indenting. If it's not the end of a line, you can't start indenting anyway. Proper layout of an expression-except that goes across multiple lines is still open for debate, but the most common cases will fit onto a single line anyway. The same applies to lambda and dict expressions, the colon won't end the line. Incidentally, quite a few of Python's control structures don't actually have "keyword then colon" anyway: for NAME in EXPR: if EXPR: while EXPR: except EXPR: except EXPR as NAME: So looking for a syntax-highlighted bit followed by a colon will pick up only a subset of cases anyway.
I think we aren't going deep enough into the rabbit hole. Why restrain ourselves to trying to cram try-except-finally into an expression, losing the finally part, when we already have a tool right under our eyes, as Nick Coghlan pointed out, which leverages try-except-finally entirely in a concise fashion, the with statement.
If context managers are amended to support a return value(maybe even transforming one?), a with-expression could look like:
last = L.pop() with default(None, IndexError) x = expr with produce_default(expensive_op, AnException) contents = f.read() with open('filename') as f d = Decimal(1) / Decimal(7) with Context(prec=5)
The syntax is cleaner, including when easing the original problem, and is capable of so much more. I seem to be lacking modesty today, because it seems like this is worth scrapping except-expression for it.
That smells like a very different proposal :) If you want to champion that one, knock together some examples (contrived or concrete) and start suggesting an expression-with construct, which could then, as you say, subsume this proposal. Be aware, though, that it's going to need some fancy magic to handle lazy evaluation of the default. Compare these constructs: # Basic notation, works fine # Note: Don't assume anything about what x is. # x could literally be any object in the system. try: x = cache[key] except LookupError: # This computation is expensive, might require # network traffic even. x = compute_and_cache(key) # Currently legal but eagerly calculates default # Also depends on cache being an actual dict x = cache.get(key, compute_and_cache(key)) # Proposed except-expression x = cache[key] except LookupError: compute_and_cache(key) # with-statement with exception_yields_default(LookupError, lambda: compute_and_cache(key)): x = cache[key] # If it fails, get the value from the context manager somehow It's plausible, but you'd have to have some system for ensuring lazy calculation. You'll find a vaguely effective helper-function example in the PEP; it's a bit ugly and uses lambdas everywhere, but it would be properly lazy. Here's another example of where lazy evaluation is important: try: opt = config[key] except LookupError: opt = input("Enter "+key+": ") opt = config[key] except LookupError: input("Enter "+key+": ") How would you do this with an expression form of with? Make sure it looks clean and evaluates lazily. ChrisA
On Fri, Feb 21, 2014 at 03:13:19AM +1100, Chris Angelico wrote:
Proper layout of an expression-except that goes across multiple lines is still open for debate, but the most common cases will fit onto a single line anyway.
Put round brackets around the entire expression, then split where appropriate. # Yes. value = (some_long_expression except LongExceptionName: 42) # Or this value = (some_long_expression except LongExceptionName: 42) # But not this: value = (some_long expression except LongExceptionName: 42) -- Steven
On 20.02.2014 15:49, Andrew Barnert wrote:
On Feb 20, 2014, at 5:45, "M.-A. Lemburg"
wrote: On 20.02.2014 02:18, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN
I'm -1 on both of them.
The colon should stay reserved for starting new blocks of statements.
So you don't like the colon in lambdas, dict displays, or slices?
Ok, that sentence was oversimplified :-) The proposed syntax looks too much like the regular try-except syntax which uses the colon to signal the start of a new block of statements.
[snip]
I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions.
Agreed. Especially after Nick Coghlan demonstrated that we already have a more readable and more concise way to do it without abusing anything:
with suppress(IOError): os.remove('/')
Who sees that and says, "I like that it's one line, but if only it could be a longer line, with more keywords, and misleadingly imply a useful value"?
[snip]
Sometimes I wish we had expression objects in Python to wrap expressions without evaluating them - sort of like lambdas without arguments but with a nicer syntax. These could then be used to implement a default() builtin.
There's no "sort of" about it; you want lambda with a nicer syntax.
.. and without the function call overhead :-)
I sympathize with that. Why do you think Haskell gets away with catch being a function when other functional languages don't? Maybe it's this:
catch \-> expensive_call \-e> (dangerous_default e)
vs. this:
catch(function() { expensive_call() }, function(e) { dangerous_default(e) }
That being said, I don't know that it would have the same benefits in Python. In a language that already encourages defining functions all over the place like JavaScript or OCaml, the verbose syntax is painful. But Python isn't like that. Haskell also has a one-character compose operator, which I love in Haskell and would love in JS, but I don't miss it in Python. (We don't even have a compose function in the stdlib.) Python lets you use functional style when it's the obvious way to express something, but doesn't force it on you when it isn't.
-- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Source (#1, Feb 20 2014)
Python Projects, Consulting and Support ... http://www.egenix.com/ mxODBC.Zope/Plone.Database.Adapter ... http://zope.egenix.com/ mxODBC, mxDateTime, mxTextTools ... http://python.egenix.com/
2014-02-12: Released mxODBC.Connect 2.0.4 ... http://egenix.com/go53 ::::: Try our mxODBC.Connect Python Database Interface for free ! :::::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/
On Fri, Feb 21, 2014 at 12:45 AM, M.-A. Lemburg
I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions.
The intended use case should really be limited to providing default values for functions or methods that are expected to return a value.
Abusing the fact that procedures in Python return None (simply because we don't have procedures and need to use functions instead) to make use of except expressions would make code less readable.
I absolutely agree here. Nothing in the proposal is _ever_ advocating that. None of the examples is even showing that. The fact that you happen to be able to is just an effect of simple and straight-forward rules.
x = data[1] except IndexError return None # is readable
That's the intent.
f = open('x.txt', 'r') except IOError return None # is probably not a good idea
Well, that'd be okay if the rest of your code is like this: if f: data = f.read(...) For instance, you might have a rule that a config file will be read if it exists, but otherwise you query the console. So you might do: foo = f.readline() if f else input("Enter the foobinator: ")
os.remove('/') except IOError return None # is really bad style
Yes. This one I don't like, and style guides should condemn it. Or just leave it up to common sense: Don't be stupid. :) ChrisA
On Thu, Feb 20, 2014 at 5:45 AM, M.-A. Lemburg
x = data[1] except IndexError return None # is readable f = open('x.txt', 'r') except IOError return None # is probably not a good idea os.remove('/') except IOError return None # is really bad style
I agree with the comments, but I think the middle one doesn't present the usage that really *would* be useful: # Fallback to other file-like object f = open('x.txt') except IOError return io.StringIO('') g = open('y.txt') except IOError return urlopen('http://the.remote/y.txt') # Sometimes we want the content of files txt = open('x.txt').read() except IOError return '' I completely agree that something one calls entirely for its side effect, not its value, is a terrible use for except expressions. -- 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.
On Thu, Feb 20, 2014 at 02:45:56PM +0100, M.-A. Lemburg wrote:
The colon should stay reserved for starting new blocks of statements.
The colon isn't reserved for starting new blocks of statements, so it can't "stay" that way. First we would have to get rid of dict displays, dict comprehensions, slices and lambda. That will require a PEP, and a period of depreciation (preferably lasting until Python 5.0, or maybe Python 9.0) before the existing syntax could be removed.
The arrow is used for return type annotations, which is a completely different concept than returning values.
Well sure, but then + is used for numeric addition and string or list concatenation, which are similarly different. It isn't a big stretch to go from "return this value" as code to "return this value" as an annotation, and an arrow operator -> is an obvious symbol for "map" or "return". I am surely not the only person who uses notation like: func -> 42 in sketches to indicate that func returns 42.
I also find it disturbing that people are actually considering to use this expression form as a way to do quick&dirty suppression of exceptions.
I'm not surprised in the least. There are bad programmers everywhere, and I'm sure some of them use Python. But "quick&dirty suppression" is not the only use-case of this, and some of us are planning to use this syntax as a way to do appropriately thought-out catching of expressions. E.g. in the use-case that Raymond originally mentioned, to stop the proliferation of "default" arguments added to everything and anything that might raise.
The intended use case should really be limited to providing default values for functions or methods that are expected to return a value.
But not expressions? If you actually mean that, that's a strange limitation. Why should this be allowed: one_over(x) except ZeroDivisionError: INF but not this? 1/x except ZeroDivisionError: INF
Abusing the fact that procedures in Python return None (simply because we don't have procedures and need to use functions instead) to make use of except expressions would make code less readable.
That I can agree with!
Sometimes I wish we had expression objects in Python to wrap expressions without evaluating them - sort of like lambdas without arguments but with a nicer syntax. These could then be used to implement a default() builtin.
Yes to this! But that requires a PEP of its own. -- Steven
On Thu, Feb 20, 2014 at 12:18:07PM +1100, Chris Angelico wrote:
On Thu, Feb 20, 2014 at 11:15 AM, Ethan Furman
wrote: result = 1/x except ZeroDivisionError -> NaN
For the record, I could just as easily live with the colon instead of the arrow.
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN result = 1/x except ZeroDivisionError: NaN
There's a nice example that just came up in some code I was looking at. Paraphrasing: if some_cond: ... elif not might_be_none.foo: ... else: ... There's a bug in the elif expression, namely 'might_be_none' might be ..None :) The current spelling to fix this is: elif not getattr(might_be_none, "foo", None): ... I'm was a fan of the colon version, but it's *really* ugly in this case: elif not might_be_none.foo except AttributeError: None: ... ': None:' is just awful. In this case, -> looks much better IMO: elif not might_be_none.foo except AttributeError -> None: ... Cheers, Phil
On 20/02/2014 17:41, Phil Connell wrote:
There's a nice example that just came up in some code I was looking at. Paraphrasing:
if some_cond: ...
elif not might_be_none.foo: ...
else: ...
There's a bug in the elif expression, namely 'might_be_none' might be ..None :)
The current spelling to fix this is:
elif not getattr(might_be_none, "foo", None): ...
I'm was a fan of the colon version, but it's *really* ugly in this case:
elif not might_be_none.foo except AttributeError: None: ...
Minor point: I would tend to add parentheses, either elif not (might_be_none.foo except AttributeError: None): # preferably or elif (not might_be_none.foo) except AttributeError: None: AFAICS they both work in this example, but I find either more explicit and therefore easier to read. (Mind you, if might_be_none was a single identifier, wouldn't it be clearer to write elif might_be_none is None or not might_be_none.foo: And if it was a more complicated expression, there is the risk that AttributeError is raised evaluating it, a probable error that would be masked by the "except" form. )
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On 20/02/14 17:41, Phil Connell wrote: <snip>
I'm was a fan of the colon version, but it's *really* ugly in this case:
elif not might_be_none.foo except AttributeError: None: ...
': None:' is just awful.
I'd spell that elif not (might_be_none.foo except AttributeError: None): ... which is a little less ugly.
In this case, -> looks much better IMO:
elif not might_be_none.foo except AttributeError -> None: ...
I'd also spell this version with parentheses. I agree this is still an advantage for `->` over `:`, but I don't think it is as big an advantage as it appeared at first glance. Regards, Ian F -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.14 (GNU/Linux) Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/ iQEcBAEBAgAGBQJTBoifAAoJEODsV4MF7PWzyqMH/251JN7U0WANH6jw4j4SK7IE w/PhiZGk7FfKYXuXMuPHj9c9cSRvCD/117yz2NXBSYeskiwcB+BWMAr3nQ6GtN/T wpYNXUfA38+Zg/n6UyP5dmCsW9JCq2gMh/gunlxfFseRgvhJQJCti8Le6pwRJFwc x+tgFtjpKWVOo70X2Aug3P0Cl/RrKmN0XNY6uFXbd9Dsw3WL0M/7M6MU5DwH2+mf gXTCBIm5/NaK7LVx2EdiJtmva+EG2TDVfQ6OI14sUB/iIh6ie+TzAyzbRh36m/rk 1dQDP88EjTaSWoG7GeeQYtrx/ycEv5z7guSmU5fMCaAPQPcD0abaojrts1xREls= =lgjS -----END PGP SIGNATURE-----
On Thu, Feb 20, 2014 at 05:41:27PM +0000, Phil Connell wrote:
There's a nice example that just came up in some code I was looking at. Paraphrasing: [...] The current spelling to fix this is:
elif not getattr(might_be_none, "foo", None): ...
And that won't go away. You can still do that.
I'm was a fan of the colon version, but it's *really* ugly in this case:
elif not might_be_none.foo except AttributeError: None: ...
': None:' is just awful.
I disagree that it's ugly. But even if you do, just don't use it in this case! Or wrap it in parens. There is no syntax ever created that cannot find itself used in an ugly way: func(("(",")")) Sometimes you just have to decide that a particular piece of code is ugly, and either don't use it, or live with the ugliness. If we're going to reject syntax because there are scenarios where it looks a bit ick, we're going to reject *everything*. -- Steven
20.02.2014 02:18, Chris Angelico wrote:
Time to open up this branch of the discussion... colon or arrow?
For the purposes of this debate, I'm comparing these two notations, and nothing else:
result = 1/x except ZeroDivisionError -> NaN
+ 0.1 from me.
result = 1/x except ZeroDivisionError: NaN
+ 0.2 from me. But I see a problem with the latter when it is used at the beginning of a block statement, such as if or while -- consider the following example: while 1 % x except ZeroDivisionError: 0: ... These two colons, very close to each other, look weird. IMHO within parens it looks better a bit but still is not ideal: while (1 % x except ZeroDivisionError: 0): ... And with arrow? while 1 % x except ZeroDivisionError -> 0: ... I believe the arrow + parens variant is nicer a bit: while (1 % x except ZeroDivisionError -> 0): ... Also, I still like the "short-paren" variant (as the parens are closer to each other which allow my eyes to match the parens easier): while 1 % x except (ZeroDivisionError: 0): ... ...or maybe: while 1 % x except (ZeroDivisionError -> 0): ... So finally... Hm... Dunno. :) Cheers. *j
Ethan Furman wrote:
result = 1/x except ZeroDivisionError -> NaN
os.unlink(some_file) except OSError -> None
result = some_func(value1, value2) except SomeError -> 42
I would have a hard time getting used to this usage of '->'. Nothing else in Python uses it in an expresson like this. It just seems very arbitrary. Here's another idea: result = 1/x except {ZeroDivisionError: NaN} You can read the part in {...} as a dictionary (although it needn't be implemented that way) so it doesn't use colons in any way they aren't being used already. -- Greg
On Thu, Feb 20, 2014 at 3:17 PM, Greg Ewing
Here's another idea:
result = 1/x except {ZeroDivisionError: NaN}
You can read the part in {...} as a dictionary (although it needn't be implemented that way) so it doesn't use colons in any way they aren't being used already.
If it even might be implemented that way, the default clause(s) would have to be eagerly evaluated. I'd like to avoid that, for several reasons: 1) In a try/except statement, the suites are parsed "try first, then except". 2) Fixing this difference with dict.get() is a valuable improvement - see the main Proposal section of the PEP. 3) Performance would be unnecessarily impacted by evaluating all the default values. 4) It makes good sense for a conditionally-executed section to be allowed to depend on its condition. Just as you can write "1/x if x else NaN", confident that it won't evaluate "1/x" unless x, you should be able to write "1/x except ZeroDivisionError: ask_user_to_choose(NaN, 0, Inf)" and depend on the user being asked only when the division by zero happens. Restricting this would force people to go back to the statement form, while leaving the code looking like it should be converted. Conversely, if you retain lazy evaluation while using a very dict-like notation will confuse people enormously. This would be... a dictionary that maps exception types to unevaluated expressions? Why can't I use one of those somewhere else!!! ChrisA
participants (35)
-
Alexander Belopolsky
-
Amber Yust
-
Andrew Barnert
-
Antony Lee
-
Ben Finney
-
Bruce Leban
-
Chris Angelico
-
David Mertz
-
Ethan Furman
-
Georg Brandl
-
Greg Ewing
-
Ian Foote
-
Jan Kaliszewski
-
M.-A. Lemburg
-
MRAB
-
Nathan Schneider
-
Neil Girdhar
-
Nick Coghlan
-
Oscar Benjamin
-
Paul Moore
-
Phil Connell
-
Philipp A.
-
Ram Rachum
-
Ram Rachum
-
Raymond Hettinger
-
Rob Cliffe
-
Ron Adam
-
spir
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Terry Reedy
-
Yann Kaiser
-
Yury Selivanov
-
Zachary Ware
-
אלעזר