Dan
Dan

Reputation: 35433

Nested try statements in python?

Is there a nicer way of doing the following:

try:
    a.method1()
except AttributeError:
    try:
        a.method2()
    except AttributeError:
        try:
            a.method3()
        except AttributeError:
            raise

It looks pretty nasty and I'd rather not do:

if hasattr(a, 'method1'):
    a.method1()
else if hasattr(a, 'method2'):
    a.method2()
else if hasattr(a, 'method3'):
    a.method3()
else:
    raise AttributeError

to maintain maximum efficiency.

Upvotes: 33

Views: 34500

Answers (6)

Zoetic
Zoetic

Reputation: 47

A compact solution:

getattr(a, 'method1',
    getattr(a, 'method2',
        getattr(a, 'method3')))()

Upvotes: 3

Ethan Furman
Ethan Furman

Reputation: 69031

method = (
        getattr(a, 'method1', None) or
        getattr(a, 'method2', None) or
        getattr(a, 'method3')
        )
method()

This will first look for method1, then method2, then method3. The search will stop as soon as one of them is found. If none of the methods are found the last getattr will raise an exception.

Upvotes: 6

dbr
dbr

Reputation: 169543

A slight change to the second looks pretty nice and simple. I really doubt you'll notice any performance difference between the two, and this is a bit nicer than a nested try/excepts

def something(a):
    for methodname in ['method1', 'method2', 'method3']:
        try:
            m = getattr(a, methodname)
        except AttributeError:
            pass
        else:
            return m()
    raise AttributeError

The other very readable way is to do..

def something(a):
    try:
        return a.method1()
    except:
        pass

    try:
        return a.method2()
    except:
        pass

    try:
        return a.method3()
    except:
        pass

    raise AttributeError

While long, it's very obvious what the function is doing.. Performance really shouldn't be an issue (if a few try/except statements slow your script down noticeably, there is probably a bigger issue with the script structure)

Upvotes: 27

David Berger
David Berger

Reputation: 12803

If you are using new-style object:

methods = ('method1','method2','method3')
for method in methods:
    try:
        b = a.__getattribute__(method)
    except AttributeError:
        continue
    else:
        b()
        break
else:
    # re-raise the AttributeError if nothing has worked
    raise AttributeError

Of course, if you aren't using a new-style object, you may try __dict__ instead of __getattribute__.

EDIT: This code might prove to be a screaming mess. If __getattribute__ or __dict__ is not found, take a wild guess what kind of error is raised.

Upvotes: 1

Jason Baker
Jason Baker

Reputation: 198557

Perhaps you could try something like this:

def call_attrs(obj, attrs_list, *args):
    for attr in attrs_list:
        if hasattr(obj, attr):
            bound_method = getattr(obj, attr)
            return bound_method(*args)

    raise AttributeError

You would call it like this:

call_attrs(a, ['method1', 'method2', 'method3'])

This will try to call the methods in the order they are in in the list. If you wanted to pass any arguments, you could just pass them along after the list like so:

call_attrs(a, ['method1', 'method2', 'method3'], arg1, arg2)

Upvotes: 22

unbeknown
unbeknown

Reputation:

How about encapsulating the calls in a function?

def method_1_2_or_3():
    try:
        a.method1()
        return
    except AttributeError:
        pass
    try:
        a.method2()
        return
    except AttributeError:
        pass
    try:
        a.method3()
    except AttributeError:
        raise

Upvotes: 4

Related Questions