开发者

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():
  开发者_Go百科  "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([])).


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"


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")


pass is fine (there's a reason it's in the language!-), but a pass-free alternative just takes a bit more nesting:

try: return get_random_foo()
except IndexError:
    try: return get_random_bar()
    except IndexError:
        raise IndexError "no foos, no bars"

Python's Zen (import this from the interactive interpreter prompt) says "flat is better than nested", but nesting is also in the language, for you to use when you decide (presumably being enlightened) that you can do better than that wise koan!-) (As in, "if you meet the Buddha on the road"...).


It looks a little weird to me that get_random_foo() is raising an IndexError when it doesn't take an index as a param (but it might make more sense in context). Why not have get_random_foo(), or a wrapper, catch the error and return None instead?

def get_random_foo_wrapper():
    try:
        return get_random_foo()
    except IndexError:
        return None

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

    return get_random_foo_wrapper() or get_random_bar_wrapper() or None

Edit: I should mention that if foo & bar are objects that may evaluate to False (0 or '' say) then the or comparison will skip over them which is BAD


If it's just those two, could always just...

try:
    return get_random_foo()
except IndexError:
    try:
        return get_random_bar()
    except IndexError:
        raise IndexError, "No foos, no bars"

If it's more than two, what you have written seems perfectly acceptable.


Building on Peter Gibson's suggestion, you could create a generic wrapper function that swallows a given exception. And then you could write a function that returns such a generic wrapper for a provided exception. Or heck, for a provided list of exceptions.

def maketrap(*exceptions):
    def trap(func, *args, **kwargs):
        try:
            return func(*args, **kwargs)
        except exceptions:
            return None
    return trap

def get_random_foo_or_bar():
    mytrap = maketrap(IndexError)
    return mytrap(get_random_foo) or mytrap(get_random_bar) or None


If you don't really need the exception message (just the type):

def get_random_foo_or_bar():
    try:
        return get_random_foo()
    except IndexError:
        return get_random_bar()    # if failing at this point,
                                   # the whole function will raise IndexError


Is it necessary that get_random_foo/bar() raise an IndexError if it's unable to succeed?

If they returned None, you could do:

def get_random_foo_or_bar():
    return get_random_foo() or get_random_bar()
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜