Add way to reenter previous try block

I cannot think of a real world example just now, but I know that on several occasions I have wanted this. Consider the following contrived example:

# x: list, tuple, dict
# y: str, int

try:
    try:
        position = y
        x[position] = 0
    except TypeError:
        try:
            position = int(y)
            x[position] = 0
        except ValueError:
            raise Exception(f"Invalid insert position {y}")
        except TypeError: 
            position = min(position, len(x))
            x = x[:position] + (0,) + x[position+1:]
except IndexError:
    position = len(x)
    x.append(0)

Yes, it’s stupid but more than stupid, its ugly. Nested try-excepts can be confusing, and cause repetition if the outer handling needs to be used in the inner handling. And what’s more, even in this silly example I would say that encapsulating each try-except into functions isn’t obviously better.

I think it would be nicer to have a way of continuing the previous try block i.e. continue the handling from the “outer” try block.

I have two ideas for this:
1: Parse try inline after an except as return to previous try block i.e.

# x: list, tuple, dict
# y: str, int

try:
    position = y
    x[position] = 0
except TypeError try:
    position = int(y)
    x[position] = 0
except ValueError:
    raise Exception(f"Invalid insert position {y}")
except TypeError: 
    position = min(position, len(x))
    x = x[:position] + (0,) + x[position+1:]
except IndexError:
    position = len(x)
    x.append(0)

I would limit the usefulness of this to where having the handling that is in the inner try blocks in the outer try block is safe. Obviously in cases where that is not true this would not allow for removing of nesting. But maybe those cases should not be flattened anyway.

2: Add way to alias try blocks, continue them under new aliases and reference them in excepts i.e.

# x: list, tuple, dict
# y: str, int

try as main_try:
    position = y
    x[position] = 0
except TypeError:
    position = int(y)
    x[position] = 0
    continue main_try as inner_try
except ValueError in inner_try:
    raise Exception(f"Invalid insert position {y}")
except TypeError in inner_try: 
    position = min(position, len(x))
    x = x[:position] + (0,) + x[position+1:]
except IndexError:
    position = len(x)
    x.append(0)

This would allow for all manner of nested try-excepts to be flattened and made possibly clearer. Both approaches would not break old code, since they are additive changes, and would allow for what I think can be simpler, more readable code in many cases.

I don’t think this comes up often enough to warrant new syntax. If the nested try block looks too ugly you can always do LBYL approach and check ahead of time if something would raise an exception, letting you use if/elif blocks.

Otherwise you might want the new pattern matching proposal that just went out. :slight_smile:

Although it hasn’t happened recently, this has happened to me a lot. The example above is a bit contrived and doesn’t point out how this would be useful for much more complex logic inside try blocks with Exception subclasses. Something like this simple dummy example:

try:
    conn = get_connection(context)
except InsecureConnectionException:
    try:
        conn = get_secure_connection(context)
    except CannotConnectException:
        handle_cannot_connect()
except CannotConnectException:
    handle_cannot_connect()

Again stupid, but even with the functional encapsulation, there is repetition. Compared to this:

try as connection_attempt:
    conn = get_connection(context)
except InsecureConnectionException:
    conn = get_secure_connection(context)
    continue connection_attempt [as secure_connection_attempt]
except CannotConnectException:
    handle_cannot_connect()

I have looked at the matching proposal and it is somewhat similar but deals with a different problem, this here is fundamentally about having a way to simplify a complex error handling “tree” without repetition.

Also, in most cases LBYL would not be cleaner I feel:

# x: list, tuple, dict
# y: str, int

if isinstance(x, dict):
    position = y
    x[position] = 0
else:
    try:
        position = min(len(x), int(y))
    except ValueError:
        raise Exception(f"Invalid insert position {y}")
if isinstance(x, tuple):
    x = x[:position] + (0,) + x[position+1:]
elif (isinstance(x, list):
    if position < len(x):
        x[position] = 0
    else:
        x.append(0)

What’s wrong with

try:
    try:
        conn = get_connection(context)
    except InsecureConnectionException:
        conn = get_secure_connection(context)
except CannotConnectException:
    handle_cannot_connect()

No repetition there.

I understand that this is a simplified example, and that you may feel that the nested tries I used are cumbersome, but you’re proposing a language change that has the potential to impact millions of people. If you can’t find a single (public) example of code that you can point to which would be improved by your proposal, and where existing mechanisms fail to provide a workaround, it’s going to be an uphill struggle to convince people that your idea is worth considering.

Personally, I look at the sort of multiply-nested code you’re using as examples, and I’d be refactoring it anyway, so the sort of maintainability issue you’re pointing out wouldn’t exist in the first place:

def get_maybe_secure_connection(context):
    """Add a docstring here explaining what's going on.

    My initial reading was that this got either an insecure or a secure connection,
    but on looking again, maybe it gets a connection, then if it raises an exception
    because the first try was insecure, it tries another method to get a secure
    connection.

    The point is, if I can't be sure, having a named function and maybe a docstring
    improves readability and maintainability :-)
    """
    try:
        conn = get_connection(context)
    except InsecureConnectionException:
        conn = get_secure_connection(context)

# Then my main code looks like this:
try:
    conn = get_maybe_secure_connection(context)
except CannotConnectExecption:
    handle_cannot_connect()
1 Like

Right you are, that removes the repetition and of course encapsulating in named functions is an improvement here. When I have run into this in the past I have indeed refactored the logic into named methods etc. however, doing so has often obscured the intent of the flow control since the exception handling is tightly coupled.

I don’t think there would be any impact on anyone that didn’t want to use it, i.e. it doesn’t break other code, it just allows for more malleability of exception handling. Although, I do agree that I need a more real-world example to back up the idea, so I will have to look around.

Code analysis apps (linters, syntax highlighters, etc) would need updating. Manuals, books and training courses would need updating. People reading (or maintaining) other people’s code would encounter it and need to know what it does.

Don’t underestimate the impact of a language change :slightly_smiling_face:

1 Like

Manuals, books and training courses would need updating.

This would propagate from the python docs over time, most learning materials are always partially playing catch up with the docs anyway. I see that this is an impact but a minor one since it isn’t a breaking change.

People reading (or maintaining) other people’s code would encounter it and need to know what it does.

Surely this would be solved by updating the python docs?

analysis apps (linters, syntax highlighters, etc) would need updating.

Here I can see this having an impact, although not really on millions of people. But an impact nonetheless.

At any rate, I will have to find some more examples.