Python: try-except as an Expression?

Question:

I find myself having this sort of pattern over and over:

variable = ""
try:
    variable = ... do some file loading stuff ...
except:
    variable = ""

Is there any way to condense this into a single expression? Like with if-else statements you can turn:

variable = ""
if something:
    variable = somethingelse
else:
    variable = ""

into

variable = somethingelse if something else ""

Is there any equivalent thing for try-catch?

Asked By: Li Haoyi

||

Answers:

Unfortunately, no, there is no language construct for it and I do not know any really legible and concise idiom either. I always wanted something like that. Some time ago someone gave me reasons to Python does not have something like variable = function_cal() except "" but they were not very convincing and I still miss this language construct 🙂

Answered By: brandizzi
def try_except(success, failure):
    try:
        return success()
    except:
        return failure()

variable = try_except(do_some_file_loading_stuff, lambda: '')

I think the code is self explanatory. It returns the value returned by success unless there is an error, then it returns the value returned by failure. If do_some_file_loading_stuff is an expression rather than just a function call, wrap it in a lambda as well.

Edit: @kindall and I improved his version a bit so it’s just as fast as mine, can be called exactly the same if you want, has more features, and is the same number of lines. Use it!

def try_except(success, failure, *exceptions):
    try:
        return success()
    except exceptions or Exception:
        return failure() if callable(failure) else failure
Answered By: agf

Since agf already provided the approach I’d recommend, here’s a version of his routine with a couple of minor enhancements:

def try_except(success, failure, *exceptions):
    try:
        return success()
    except exceptions or Exception:
        return failure() if callable(failure) else failure

This version:

  1. Lets you specify exactly which exceptions will be caught as additional optional arguments. You should always catch the minimum set of exceptions that will do the job and let exceptions you can’t handle bubble up to the caller.

  2. Supports the use of a plain value as well as a function for the failure value. This saves you having to use a lambda in a lot of cases. (Of course, instead of lambda: '' you can just use str.)

Answered By: kindall

Obviously, the latter way of doing it is more succinct and is preferred by many programmers like you.

While the language itself doesn’t provide a “one-line try-except-catch” for whatever the reasons, all good and necessary, I presume, you can generally accomplish this, by changing the program, a little; like for example:

try:
    a_var = a_dict.get('abcd')
except a_dict.KeyError:
    a_var = ''

into:

a_var = a_dict.get('abcd',default='')

and similarly for the DB queries;

try:
    a_qs = Model.objects.get(id=42)
except Model.DoesNotExist:
    a_qs = Model.objects.create(id=42)

with

a_qs = Model.objects.get_or_create(id=42,**kwargs)

and add similar API’s to your own programs where possible. try-except is rather “cheap” in Python and is exception based programming is preferred over the “check-first” approach generally suggested in Java like languages because of the expensive nature of the exception handling. So, you should rather wrap this “catching thing” in a method/function and call that every where, like for example the dict and other constructs do.

Answered By: lprsd

There is no easy way to simplify a try/catch statement like the if/else example, but I want to point out that python’s “with” statement introduced in python2.5 made a lot of interfaces to file/db io try catch statements simpler and exception safe. IO operations tend to be where a lot of try/catch statements are used.

with open("myfile.txt", "r") as f:
   # Do stuff with f

instead of

try:
   f = open("myfile.txt", "r")
   # Do stuff with f
except:
   pass
finally:
   if f: f.close
Answered By: Charles Ma

Here’s a context manager that provides a little bit of a shortcut:

from contextlib import contextmanager

@contextmanager
def catch(*exceptions, **kwargs):
    try:
        yield kwargs.get("default", None)
    except exceptions or Exception:
        pass

Usage:

with catch(ZeroDivisionError, default=0) as x:
    x = 3 / 0              # error

print x                    # prints 0, the default

The basic idea here is that the context manager returns whatever default value you pass it, which is then assigned to the variable you specified in the with statement’s as clause. Then, inside the context, you execute a statement that tries to assign to that same variable. If it raises an exception, the context manager catches and silently ignores it, but since the assignment didn’t happen, the default value remains.

Might be particularly useful for multi-line calculations that nevertheless result in a single value.

Answered By: kindall

Inspired by with-handlers in Racket:

def with_handlers(handlers: Dict[Type[Exception], Callable[[Exception], S]], f: Callable[[], T]) -> Union[S, T]:
    try:
        return f()
    except tuple(handlers.keys()) as err:
        return handlers[type(err)](err)

handlers contains all expected exceptions and their corresponding handlers.

And here is a catch function, which also supports else and final clauses:

def catch(
        try_clause: Callable[[], T],
        except_clauses: Dict[Type[Exception], Callable[[Exception], S]],
        else_clause: Optional[Callable[[T], R]] = None,
        final_clause: Optional[Callable[[Union[T, S]], None]] = None) -> Union[R, S, T]:
    try:
        ret = try_clause()
    except tuple(except_clauses.keys()) as err:
        handled = except_clauses[type(err)](err)
        return handled
    else:
        return ret if else_clause is None else else_clause(ret)
    finally:
        if final_clause is not None:
            try:
                final_clause(ret)
            except NameError:
                final_clause(handled)

In Python, try statements do not introduce their own scope,
but here try_clause is a function, so else_clause needs a parameter to receive information from try_clause.
Similarly, final_clause needs two parameters.

Answered By: weakish
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.