Steven Rogers
Steven Rogers

Reputation: 1994

Best way to write "try elsetry" in python?

I know 'tryelse' is not a real thing, but my question is about the best way to write out this logic. Given the following example, foo and bar are functions that could break.

I want aa to be foo(), but if that breaks, I want it to become bar(), but if that one breaks too, then set aa to 0 as a default.

try:
    aa = foo()
elsetry:
    aa = bar()
except e:
    aa = 0

Restating my question, what is the best real way to write out this logic in python?

Upvotes: 1

Views: 141

Answers (3)

abarnert
abarnert

Reputation: 365697

If you have either a long chain of these, or a dynamic list of alternatives, you probably want to use a for loop:

for func in funcs:
    try:
        aa = func()
        break
    except:
        pass
else:
    aa = 0

Note that either way, you probably don't really want a bare except here. Usually you're only expecting some specific class of errors (e.g., ValueError or LookupError), and anything else shouldn't mean "silently try the next one" but "show the programmer that something unexpected went wrong". But you know how to fix that.


You could of course wrap that up in a function if you need to use it repeatedly:

def try_funcs(*funcs, defval=0, ok_exceptions=Exception):
    for func in funcs:
        try:
            return func()
        except ok_exceptions:  # ok_exceptions can also be a tuple of exception classes.
            pass
    return defval

Of course, as mentioned in the comments, this does require that everything you want to try be a function of no arguments. What if you want to try spam(42), then eggs(beans) if that fails? Or something that isn't even a function call but some other expression, like foo[bar]?

That's the same as the general case that occurs all over Python: you use partial to bind in the arguments for the first case, or write a wrapper function with lambda for the second:

result = try_funcs(partial(spam, 42), partial(eggs, beans), lambda: foo[bar])

However, if you just have a static two or three alternatives to try, Simon Visser's simple nested answer is much clearer.


If you're asking why the language doesn't have your tryelse… well, I know it's come up on the python-ideas/-dev lists and elsewhere a few times, and been discussed, so you might want to search for more detailed/authoritative answers. But I think it comes down to this: While it's theoretically possible to come up with cases that might look cleaner with a tryelse, every example anyone's ever given is either fine as-is, or should obviously be refactored, so there's no compelling argument to change the grammar, reserve a new keyword, etc.

Upvotes: 4

Simeon Visser
Simeon Visser

Reputation: 122336

The nested approach is still best:

try:
    aa = foo()
except Exception:
    try:
        aa = bar()
    except Exception:
        aa = 0

Despite trying to do it with less nesting, the above expresses what you wish to do and it's clear to the reader. If you try to nest more it becomes awkward to write and that's time to rethink your approach. But nesting two try/excepts is fine.

You can also write:

try:
    aa = foo()
except Exception:
    aa = None

if aa is None:
    try:
        aa = bar()
    except Exception:
        aa = 0

but somehow that doesn't look right (to me at least). It would also be incorrect in case foo() can return None as a valid value for aa.

Upvotes: 3

Piotr Dabkowski
Piotr Dabkowski

Reputation: 5939

I think this is the best way to do that:

try:
    aa = foo()
except:
    try:
        aa = bar()
    except:
        aa = 0

This is what you do when you dont have elif statement like in JavaScript:

if if_condition:
   code
else:
   if elif_condition:
       code
   else:
        code

Upvotes: 1

Related Questions