I’ve been working with OSErrors a lot recently, and the ergonomics keep annoying me, since I have to write this kind of boilerplate over and over:
try:
...
except OSError as exc:
if exc.errno == ENOSYS:
# handle it
else:
raise
The Python 3 rework of the OSError hierarchy helps a lot for common errnos (e.g. you can do except FileExistsError instead of having to check exc.errno == EEXIST), but that only helps for the specific errnos that have built-in types.
Idea
What if we could instead write:
try:
...
except OSError as exc if exc.errno == ENOSYS:
# handle it
Details
Given a try block like:
try:
...
except ETYPES1 as VAR1 if COND1:
...
except ETYPES2 as VAR2 if COND2:
...
...
The meaning is: if an exception is raised inside the try body, then we iterate through the except clauses from top to bottom. For each one:
Check if the exception’s class is a subclass of any ETYPES. If not, continue onto the next except clause.
Bind the exception object to VAR.
[THE NEW PART] Execute COND. If it returns something false-y, then unbind VAR and continue onto the next except clause.
The arguments given don’t really apply to Python, though. First, Python doesn’t have a two-pass exception handling mechanism. Second, we like to have finally clauses executed when an exception terminates the program.
I found this proposal for Ruby that seems similar, but it did not catch any steam, which I found curious since this kind of syntax seems quite Ruby-esque to me The main objection seems to be that this signals an anti-pattern and it’s better to refactor the exception to be more fine-grained, like how Python introduced FileNotFoundError to eliminate the super common if e.errno != ENOENT: raise pattern. It has a point.
The except ... if ... syntax also reminds me a lot of conditional pattern matching, especially when you use it to catch errors:
try {
...
} catch {
case e: EXC if COND => ...
case e: EXC => ...
case _ => ...
}
I know there’s a school that discourages this since it could lead to confusing flows if used to liberally (what happens if COND throws is a common example), but IMO it’s not that bad.
The same as if an exception was raised in the error handler:
>>> try:
... 1/0
... except ZeroDivisionError as exc:
... exc + 1
...
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 4, in <module>
TypeError: unsupported operand type(s) for +: 'ZeroDivisionError' and 'int'
>>> exc
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'exc' is not defined
Yeah. And in fact, it’s already possible for exceptions to be raised inside an except clause filter, so this wouldn’t be a new thing:
>>> try:
... raise RuntimeError
... except RnutimeError:
... pass
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
RuntimeError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
NameError: name 'RnutimeError' is not defined