Adding `else` option to `with` clause

Writing context manager, I was thinking to myself, we could use an else option in with.
This clause would be executed only if an exception hasn’t occurred while executing the context. This is true even if this exception was silently handled by the context manager. In this case, there is no way for the outer context to know anything about it, as the context manager would be invalid when the context ended.
This is similar to the way else works in for, and would provide a solution to some not that far-fetched use cases.

While I find the feature being useful, I also find it confusing were it implemented in this way.

for and while loops have a condition that is being evaluated in each iteration. The else clause is executed when the condition is evaluated to be False, hence why it fits.

A bunch of except statements after a try can be looked as an underlying elif isinstance(e, ...) statements in which else is executed if e is not an instance of any exception (cause there is no actual exception).

I’m not so sure the same can be said on with statements, unless you also allow for except statements (so they execute if the context manager has raised a matching exception) which would be kinda weird and put try statements in a weird place.

However, I believe there was a recent attempt to rework the context manager protocol though I don’t remember the details. Perhaps this rework addresses your need as well?

3 Likes

This feature could be useful in scenarios where you want to perform certain actions only if the context completes successfully and you want to differentiate between successful and unsuccessful context executions. I don’t except it to have any error externalize to the outer context as these errors where already handled by the context manager.
BTW, my scenario is

        response = None
        with SimpleExceptionContext("IB query failed",never_throw=True):
            response = client.download(self.token_id, self.query_id)
        if not response:
            return None

which I think is pretty cool. (SimpleExceptionContext just prints exception details if exception happened though is quite configurable. I also have it as decorator on functions.).

I agree that else is more adequate in case of while, though it has never occurred to me before. It could be other keyword such as onsuccess. Or it could support an except clause like you suggested. In this case I think you should only catch unhandled exceptions , and in case of handled to have like default: (thinking about switch case).

How is this different from:

try:
    response = client.download(self.token_id, self.query_id)
except:
    pass
else:
    return None

This looks like you want to prevent exceptions and simply return None. That doesn’t seem like a good thing for very many situations - and definitely not something that’s worth having syntactic support for.

1 Like

How is it different. I am glad you asked.

First of all , it writes to log the message and traceback. It is quite nagging to write all day :

except: 
    import traceback
    traceback.print_exc() 

It also writes a detailed traceback(using pyro5 that includes local fields in every frame ), it also let the exception occur if I am debugging. The decorator also has option to return value in case of exception.

The second thing is that there are many places in the program (for example button click callback) , where you don’t want to kill the program even if error has occurred. You can write a very detailed exception to the log so that you can later debug. And you want to do it with minimal effort.

People assume that the code they write is perfect , and they don’t prepare for a situation of an unknown exception. I see where my exception happen and make sure they won’t kill the program ( I know well where they will be handled). Especially as I do it in my spare time and not aiming for perfection( not a critical component etc).

def simple_exception_handling(err_description=None,return_succ=None,never_throw=False,always_throw=False,debug=False,detailed=True,err_to_ignore=[]):

this is the signature of the decorator btw. https://github.com/eyalk11/compare-my-stocks/blob/master/src/compare_my_stocks/common/simpleexceptioncontext.py

I usually have this sort of thing only at a very few places, so for example, at the entire event loop level. That way, “minimal effort” becomes irrelevant, since you do it in usually just one place, and a normal try/except is completely fine. If you find that you have to put exception-swallowing code in many places, that is a code smell.

Well ,QT breaks if there is unhandled exception in event handler. There are a lot of event handlers.
Anyway, everyone is entitled to their (stupid or not) opinion.

Yes, and “code smell” does not mean “never do this”. However, it does mean that it’s a poor justification for new syntax.

Or this is a beautiful code. could be. I think that code should be in every project.

It is not.

The general usage is as follows:

try:
    with object:
        ...
except Exception:
    ...
else:
    ...

With the proposed syntax, it will become:

try:
    with object:
        ...
    else:
        ...
except Exception:
    ...
else:
    ...

Why do we need two “else” statements?

Well if an exception occurs inside the with and was handled by the context manager, you would have no way of knowing this.

You would have no way of knowing about it because you are handling exceptions using “SimpleExceptionContext,” which is essentially acting as a “try” statement.

Instead of mimicking the “try” statement, you should propagate the exceptions after handling them with “SimpleExceptionContext.”

That is the behavior by default of course, but you can control it. Sometimes you don’t want to throw exceptions. But it is relevant for context manager in general, not just my use case. They all can handle exceptions silently.

I don’t believe we should encourage this. I am against silencing exceptions.

Maybe, the else stmt could be used in the same way it is used with the try stmt:

phrases = {"greet": "hello, world"}
with open("wrong_key.txt", "w") as f:
    f.write(phrases["nod"])
except KeyError:
    print("unrecognised phrase key")
else:
    print("great work")

It is part of the language, if __exit__ returns True (IIRC) then the exception is silently handled.

It could be. But there is the extra factor of silently handling exceptions.

If __exit__() returns true, the exception is not silently handled; it’s not propagated. You should handle it inside that method.

That being said, the method does not encourage silencing exceptions. Instead, it provides all the necessary components to handle the exception: object.__exit__(self, exc_type, exc_value, traceback) .

1 Like

I generally think that if the language provided a mechanism to do something , but hide it from the outside context in a completely bulletproof way , it seem that something is wrong.
Especially, as it is something the outside context should arguably know.

The fact that we don’t find the correct term to put in the clause shouldn’t obstruct us from acknowledging the essence, and then we can think about the formulation.