views:

228

answers:

5

I am finding myself doing the following a bit too often:

attr = getattr(obj, 'attr', None)
if attr is not None:
    attr()
    # Do something, either attr(), or func(attr), or whatever
else:
    # Do something else

Is there a more pythonic way of writing that? Is this better? (At least not in performance, IMO.)

try:
    obj.attr() # or whatever
except AttributeError:
    # Do something else
A: 

There is another nice idiom:

if hasattr(obj, 'attr'):
    ...something...
else:
    ...something else...

From the two options you posted I prefer the first one. The notion of throwing exceptions when accessing members just do not seem the thing the exceptions were meant to be used for.

pajton
-1 since AttributeError is a very obvious exception to be used "when accessing members".
Peter Hansen
This is kind of open question. I don't find using `AttributeError` as a good practise in this situation, you do. Does it mean you're right and I am not? I don't think so
pajton
+1  A: 

Using try/except is generally considered more pythonic. It is also more efficient if obj does have the attribute, since it eliminates the test - try blocks have no overhead if the exception is not thrown. On the other hand it is less efficient if obj does not have the attribute since it will have the overhead of throwing and catching the exception.

Dave Kirby
Maybe it is pythonic, but it is ugly and I believe exceptions aren't meant for that kind of purpose. In python you can even catch `IndentationError`, is this also pythonic to surround some code you are not sure is properly indented?
pajton
Why isn't it? Let's say you are importing a plugin module, and the module fails to import. The thrown exception says why. If you were motivated to do so, you could catch the error and tell the user exactly how to fix it ("Clear up the indent problem on line X of file Y").I appreciate this kind of flexibility.
Joe Koberg
And i challenge you to "surround some code" that is not properly indented with a try block.... You have to be more indirect than that because the parser will catch the problem before the `try:` structure is completed.
Joe Koberg
This is actually a real world example. I was testing some code in a project, having no clue why it was failing...and why my debug statements are not printed. And it turned out that somebody did `try: ... except Exception:` somewhere and when I corrected it to catch only particular exception it turned out there was an `IndentationError`:-). Funny, but scary.
pajton
+7  A: 

The try/except alternative, as you code it, might accidentally cause an AttributeError caused by problems in anything that attr() is calling, to be caught. If you want to code with try/except, which is reasonable and quite well performing if it's rare for the attribute to be missing, use:

try:
  attr = obj.attr
except AttributeError:
  dosome(thingelse)
else:
  attr()

this will not cause "accidental catching" as above. As to which approach is preferable, I generally go with the exception-catching approach - "EAFP", "it's Easier to Ask Forgiveness than Permission" (a motto often attributed to Admiral Grace Hopper, the driving force behind COBOL and CODASYL) definitely applies well to Python;-).

Alex Martelli
A: 

Ah, it depends on the exact code. Your two tools:

  • hasattr(obj, 'attr') return True if and only if obj.attr exists.
  • getattr(obj, 'attr', other_value) returns obj.attr if it exists, else other_value
  • try a = obj.attr/except failure()/else do_something(a) when performance beats readability.

Here are the most common cases:

 the_name = getattr(user, 'name', '<Unknown User>')
 user.name = getattr(user, 'name', '<Unknown User>')
 if not hasattr(name, 'user'):
    try_asking_again()
 name = user.name if hasattr(user, 'name') else do_expensive_name_lookup(user)

To better understand the whole process, look at this snippet:

class Thing():
    def __init__(self):
        self.a = 'A'

    def __getattr__(self, attr):
        if attr == "b":
            return "B"
        else:
            raise AttributeError("Thing instance has no attribute '" + attr + "'")

item = Thing()
print "hasattr(a) is " + str(hasattr(item, "a"))
print "a is " + item.a
print "hasattr(b) is " + str(hasattr(item, "b"))
print "b is " + item.b
out = "c is " + item.c if hasattr(item, "c") else "No C"
print out
print "and c is also " + getattr(item, "c", "Not Assigned")
print "c throws an Attribute exception " + item.c

which has this output:

hasattr(a) is True
a is A
hasattr(b) is True
b is B
No C
and c is also Not Assigned
Traceback (most recent call last):
  File "attr_snippet.py", line 23, in <module>
    print "c throws an Attribute exception " + item.c
  File "attr_snippet.py", line 9, in __getattr__
    raise AttributeError("Thing instance has no attribute '" + attr + "'")
AttributeError: Thing instance has no attribute 'c'
Charles Merriam
+5  A: 

Since you are calling the attr, you could just do:

def default_action():
    # do something else

action = getattr(obj, 'attr', default_action)

action()
Joe Koberg
I never thought of this, but this sure works out perfectly for some of my use cases!
MTsoul