Idea of new keywords for handling exceptions (skip and elskip)

Doesn’t if…elif…else work like this?

if evaluation_function_A():
   # code 1
elif evaluation_function_B():
   # code 2
else:
   # code 3

The code in evaluation_function_B() only runs if evaluation_function_A() returns False

Yeah. I guess there are a few cases when the first and last blocks are linked, like for-else and try-finally. I don’t think there are any where multiple blocks are linked, but there are only a handful of statement types anyway.

That’s different, because with elskip the code inside the block executes. Otherwise the next exception wouldn’t get raised.

edit: put another way: only one of code 1 | code 2 | code 3 can be executed in the if/elif/else block. In your example in the OP, potentially every block is executed.

Kind of, but the block inside doesn’t, only the conditions. A try has no condition, and an except only has the “what kind of exception was it?” condition. Situations in which you go into one indented block, then into another, in the same construct, are quite unusual.

1 Like

I understand.

I think the way exceptions are handled do cause a bit this less common behavior. Even the try…except works like that: you jump from inside the try: code block to the inside of the except block. It is definitely weirder, but I think this has more to do with the weirdness of how exceptions are treated than the proposed syntax itself

I understand. I replied to the same clarification by Chris below.

I think this could be made to work:

with tries() as t, t(MyErr1):
    res = func1()
with t, t(MyErr2):
    res = func2()

For example if the first block succeeds, then t will know that, so the next t(...) call can raise something so that the second block gets skipped (that’s why the t is there in with t,: to catch that something).

(That doesn’t do a default, but I think that’s less interesting, could be done in various ways.)

Or just this (the above is really an attempt to do the below with less code):

success = False
with suppress(MyErr1):
    res = func1()
    success = True
if not success:
    with suppress(MyErr2):
        res = func2()
        success = True
if not success:
    res = DEFAULT_VALUE
1 Like

As a very basic point, your proposal breaks every piece of code that uses the variable name skip. And while it might be possible to do some context-dependent games to make skip be the keyword in this situation, but the variable elsewhere, you’d still break anyone who renames a variable called skip with search and replace, and refactoring tools that “intelligently” rename variables would need to take this new syntax into account, etc.

This is just a very small part of the impact of a new keyword, so as you can imagine the benefits need to be pretty substantial to justify one.

I understand! Thanks for the explanation.
If I understood correctly it would probably only make sense to do it enhancing an existing keyword instead, and not adding a new keyword?

Would this be a viable alternative?

try pass MyException1:
    # Some Code that might throw MyException1 ....
else try pass MyException2:
    # Some Code that might throw MyException2 ....
else try pass MyException3:
    # Some Code that might throw MyException3 ....
else:
    # Some Code that handles the case when nothing worked
except:
    # Catches all other Exceptions

Also, there are readability issues with for-else, in that not everyone gets its meaning right.

I got for-else wrong just the other day answering a help question (and Chris kindly corrected me), and I have written python as a part of my job for a few years.

Subtlety is a bad property for control flow structures to have, so any new syntax should strive for maximum clarity. Like for-else, this seems prone to misinterpretations which will lead to people like me making embarrassingly incorrect forum posts. Obviously a strong point against. :wink:


The example code structure does sometimes arise. But in every real case where I have seen it, there is a useful and clarifying function name which can be given to the inner part of the exception handler.

e.g.

try:
    foo()
except FooError:
    bar()

It is extremely rare that this is written more clearly in a nested structure, with the definition of bar inlined. And those rare cases aside, I’ve never seen a case, at least that I can recall, where there is more than one level of nesting and the inlined version is clearly superior.

Even if you can construct or find cases – finding a few in prominent packages or the stdlib would be a good start if you want to propose a language change – you’ll also need to make a convincing argument that nested exception handlers are hard to read, or that this syntax offers a significant improvement or new capability beyond readability. I find that unlikely to succeed, although you are welcome to try.

Even much more modest proposals for change to portion have a low success rate. IMO, that’s a good thing. As the addage goes, “No is temporary, yes is forever.”

1 Like

I think you’re somewhat missing the point here. Yes, re-using keywords will help with the issues involved in having a new keyword, but there are plenty of other implications of new syntax (teaching materials need updating, code that parses Python needs amending, etc, etc).

My point was that you need a much stronger set of benefits if you want to propose any sort of new syntax. Simply reducing the drawbacks of the new syntax won’t be sufficient.

In this case, there are ways of writing the code you want without this proposal, which people have been happily using for many years, so you need to demonstrate that your code is significantly better than what you can do now, for a reasonably wide range of use cases. Simply avoiding something that is “IMHO quite cumbersome and unpleasant to work with” is way below the level of benefit that would justify any sort of new syntax, frankly.

Maybe you could work on finding objective benefits for code using your proposal, and finding examples of real-world code in significant Python projects (the stdlib is often a good starting point, but 3rd party projects are also worth looking at) that would be improved by using your proposed feature? Or find other languages that have implemented the feature you’re proposing, and describe how they have benefitted from it.

But I’ll be honest, I suspect you’re unlikely to be able to present a compelling case for this. I really don’t see that the code you have to use now is that bad. And if the nesting is a problem, that’s probably an indication that some of the more deeply nested blocks would benefit from being refactored into their own function.

3 Likes

That’s exactly my point. Avoid using MyErr1 for control flow; instead, return None .
The try/except block should be reserved for handling truly exceptional cases.

The block of code above would become:

def func1():
    try:
        # a = 1  # uncomment to return a
        a += a
        return a
    except NameError:
        return None
    
func2 = func1
    
res = ''
if not (res := func1()):
    if not (res := func2()):
        res = 'DEFAULT_VALUE'
    
print(res)
1 Like