This change would bring the syntax more in line with other comma-separated lists in Python, such as function arguments, generator expressions inside of a function call, and tuple literals, where parentheses are optional.
When are parentheses optional around lists of function arguments?
Also, as others have mentioned, import , with and case would seem like more relevant comparisons than any of these examples.
I must admit I have accidentally written the proposed form before, except A, B, C:.
The semantics of as keyword are perhaps debatable, somehow I naively think that as keyword binds tighter than , comma, making except A, E as e: as a bit of a WAT moment.
The precedent would be with open("a"), open("b") as f: that folks may be used to.
Then again, maybe it’s just me, and maybe folks will get used to it.
My position is that the syntax itself should support not using parentheses, for consistency, but style guides (including PEP 8) and linters should strongly encourage using them, and I would also like the PEP to talk about this potential point of confusion.
While I initially voted in favour of “permissive compiler, restrictive linting” where as is concerned, there’s a concrete “least work for most benefit” gain from continuing to require parentheses with the as clause: any work associated with updating style guides to specify that guideline and linters to enforce it won’t need to happen, since compilers will continue to handle it on everyone’s behalf.
That’s actually a pretty decent “practicality beats purity” argument in favour of enforcing this particular linting guideline at the compiler level - the parentheses are really only annoying when you’re not using as with multiple exception types, so being required to add them when you are is just forcing you to do something you should be doing for readability reasons anyway.
“It should be a linting rule not a syntax restriction”, while true, doesn’t feel like a particularly compelling reason to inflict a bunch of avoidable work on people, so I changed my vote in the informal poll accordingly (even if it does make the language grammar a little messier than it would otherwise be).
>>> from os import getuid, getenvb as getenv_bytes
>>> with open('/dev/null', 'rb') as empty_f, open('/dev/zero', 'rb') as full_f:
... empty_f.read(), full_f.read(1)
...
(b'', b'\x00')
Making except ValueError, CustomError as exc:not require parenthesis around the list of Error classes makes the binding behavior of as inconsistent in different areas of the language so that it requires more context to understand.
I don’t think it will really be confusing, but it is an inconsistency nonetheless. I don’t think anyone enjoys typing those ()s. When editing code to add an additional exception class to an except clause where there is only one, it requires more keyboard gymnastics or editor tricks when they are required. Lightening up the syntax requirement would alleviate that.
(all of this may have been stated already, I haven’t read the whole thread. This is just me justifying my thought process outloud)
One thing I realized is we aren’t just putting “parentheses around around the list of Error classes”, you’re making a tuple. And you aren’t binding what that tuple is but some relation of what that except clause does with that tuple.
Another option is to only require parentheses when as is involved, which makes the parentheses necessary to disambiguate and thus not an odd use of them.
A bit of a side tracking, and looking into the future with PEP 760 - The main python branch contains 399 bare except clauses? That is kind of worrying, I hope they aren’t in any relevant libraries…
Yes, and that is often, but not always the case. Quick spot check with github search makes me guess 1/8 being problematic in abstract (i.e. no raise), and maybe another 1/8 of those are in potentially public facing parts, although in unlikely code paths if I am seeing it correctly. But not something to worry about now.
pablogsal
(Pablo Galindo Salgado)
Split this topic
35
Hah, I guess that is actually true. I never gave the underlying mechanics much thought. Indeed you can bind a tuple to a name and use it in an except clause for the same effect. I don’t know that I’ve ever seen code written that way but it is supported and I’m sure some code uses it. I presume the original thinking if not implementation was that the class or tuple in an except clause is basically same as what you could pass to isinstance().
>>> t = (ValueError, RuntimeError)
>>> try:
... raise RuntimeError("knigit")
... except t as e:
... print(e)
...
knigit
>>>
I do like the “only if needed to disambiguate” idea as an intermediate option if not requiring parens here ever isn’t acceptable.
It’s quite useful to write functions like unittest’s assertRaises (though assertRaisesitself is too complex for a simple try/except).
Almost, but there’s a weird inconsistency: except doesn’t accept nested tuples. You can do:
>>> errors = AttributeError, ArithmeticError
>>> try:
... 1/0
... except errors as err:
... print(err)
...
division by zero
but not:
>>> try:
... 1/0
... except (errors, ValueError) as err:
... print(err)
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
TypeError: catching classes that do not inherit from BaseException is not allowed
Why should nested anything be accepted in a context where something like an iterable producing flat members is almost always expected?
There are other scenarios like you mention, such as things with multiple inheritance, that are not necessarily flat by nature. Or, a tree structure may require flattening in a depth first or breadth first or other manner. In such cases, if a flat version is needed, and it is obvious which is the unambiguous method for flattening it, go for it.
Certainly if efficiency is not a consideration and some very broad generality is valued, you could flatten such a nested tuple before further evaluation or allow much more complex processing such as removing duplicates you can generate.
But is there much value when, instead, the occasional user does it manually so that the more common case is simpler and can be handled efficiently?
This is a side question about how tuples are handled in python, perhaps in general.
My previous understanding is that what makes a tuple is the comma, not the parentheses.
So, unless the handling of tuples in the except case is different, is there a difference between evaluating cases like these:
a, _, c = 1, 2, 3
(a, _, c) = (1, 2, 3)
There can be other variations but the question I have is whether you can say one of many similar methods will create a (temporary) tuple while others don’t?
The case you are discussing can perhaps be easy to investigate by looking at the byte code produced in these two variant of ... except alpha, beta ...
where the ... just means there will be more code on lines above and below, versus:
... except (alpha, beta) ...
Python does overload usage of parentheses in various ways but in many contexts, such as using parentheses for grouping, the code produced if you are not changing precedence rules would probably be the same but the parentheses used would make it easier for human readers of the code to be clear on what it will do.
I am not expressing an opinion here on whether the parentheses here should be optional, but just asking if this observation is correct, or if a tuple is automatically created just because of the comma.