Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a neater alternative to `except: pass`?

I had a function that returned a random member of several groups in order of preference. It went something like this:

def get_random_foo_or_bar():
    "I'd rather have a foo than a bar."

    if there_are_foos():
        return get_random_foo()

    if there_are_bars():
        return get_random_bar()

    raise IndexError, "No foos, no bars"

However, the first thing get_random_foo does is verify there are foos and raise an IndexError if not, so there_are_foos is redundant. Moreover, a database is involved and using separate functions creates a concurrency issue. Accordingly, I rewrote it something like this:

def get_random_foo_or_bar():
    "Still prefer foos."

    try:
        return get_random_foo()
    except IndexError:
        pass

    try:
        return get_random_bar()
    except IndexError:
        pass

    raise IndexError, "No foos, no bars"

But I find this much less readable, and as I've never had reason to use pass before it feels instictively wrong.

Is there a neater efficient pattern, or should I learn to accept pass?

Note: I'd like to avoid any nesting since other types may be added later.


Edit

Thanks everyone who said that pass is fine - that's reassuring!

Also thanks to those who suggested replacing the exception with a return value of None. I can see how this is a useful pattern, but I would argue it's semantically wrong in this situation: the functions have been asked to perform an impossible task so they should raise an exception. I prefer to follow the behaviour of the random module (eg. random.choice([])).

like image 212
Ian Mackinnon Avatar asked Sep 16 '10 02:09

Ian Mackinnon


2 Answers

That is exactly how I would write it. It's simple and it makes sense. I see no problem with the pass statements.

If you want to reduce the repetition and you anticipate adding future types, you could roll this up into a loop. Then you could change the pass to a functionally-equivalent continue statement, if that's more pleasing to your eyes:

for getter in (get_random_foo, get_random_bar):
    try:
        return getter()
    except IndexError:
        continue  # Ignore the exception and try the next type.

raise IndexError, "No foos, no bars"
like image 194
John Kugelman Avatar answered Sep 22 '22 02:09

John Kugelman


Using try, except, pass is acceptable, but there is a cleaner way to write this using contextlib.suppress() available for python 3.4+.

from contextlib import suppress

def get_random_foo_or_bar():
    "Still prefer foos."

    with suppress(IndexError):
        return get_random_foo()

    with suppress(IndexError):
        return get_random_bar()

    raise IndexError("No foos, no bars")
like image 26
Legended Avatar answered Sep 22 '22 02:09

Legended