87

Which is a better way to check for the existence of an attribute?

Jarret Hardie provided this answer:

if hasattr(a, 'property'):
    a.property

I see that it can also be done this way:

if 'property' in a.__dict__:
    a.property

Is one approach typically used more than others?

Community
  • 1
  • 1
Andrew Halloran
  • 1,518
  • 1
  • 14
  • 16
  • Your second option is wrong, anyway the [second answer](http://stackoverflow.com/a/610923/1132524) from the link you provided answer to your question. – Rik Poggi Mar 17 '12 at 09:24

4 Answers4

174

There is no "best" way, because you are never just checking to see if an attribute exists; it is always a part of some larger program. There are several correct ways and one notable incorrect way.

The wrong way

if 'property' in a.__dict__:
    a.property

Here is a demonstration which shows this technique failing:

class A(object):
    @property
    def prop(self):
        return 3

a = A()
print "'prop' in a.__dict__ =", 'prop' in a.__dict__
print "hasattr(a, 'prop') =", hasattr(a, 'prop')
print "a.prop =", a.prop

Output:

'prop' in a.__dict__ = False
hasattr(a, 'prop') = True
a.prop = 3

Most of the time, you don't want to mess with __dict__. It's a special attribute for doing special things, and checking to see if an attribute exists is fairly mundane.

The EAFP way

A common idiom in Python is "easier to ask for forgiveness than permission", or EAFP for short. You will see lots of Python code that uses this idiom, and not just for checking attribute existence.

# Cached attribute
try:
    big_object = self.big_object
    # or getattr(self, 'big_object')
except AttributeError:
    # Creating the Big Object takes five days
    # and three hundred pounds of over-ripe melons.
    big_object = CreateBigObject()
    self.big_object = big_object
big_object.do_something()

Note that this is exactly the same idiom for opening a file that may not exist.

try:
    f = open('some_file', 'r')
except IOError as ex:
    if ex.errno != errno.ENOENT:
        raise
    # it doesn't exist
else:
    # it does and it's open

Also, for converting strings to integers.

try:
    i = int(s)
except ValueError:
    print "Not an integer! Please try again."
    sys.exit(1)

Even importing optional modules...

try:
    import readline
except ImportError:
    pass

The LBYL way

The hasattr method, of course, works too. This technique is called "look before you leap", or LBYL for short.

# Cached attribute
if not hasattr(self, 'big_object'):
    big_object = CreateBigObject()
    self.big_object = CreateBigObject()
big_object.do_something()

(The hasattr builtin actually behaves strangely in Python versions prior to 3.2 with regard to exceptions -- it will catch exceptions that it shouldn't -- but this is probably irrelevant, since such exceptions are unlikely. The hasattr technique is also slower than try/except, but you don't call it often enough to care and the difference isn't very big. Finally, hasattr isn't atomic so it could throw AttributeError if another thread deletes the attribute, but this is a far-fetched scenario and you'll need to be very careful around threads anyway. I don't consider any of these three differences to be worth worrying about.)

Using hasattr is much simpler than try/except, as long as all you need to know is whether the attribute exists. The big issue for me is that the LBYL technique looks "strange", since as a Python programmer I'm more used to reading the EAFP technique. If you rewrite the above examples so that they use the LBYL style, you get code that is either clumsy, outright incorrect, or too difficult to write.

# Seems rather fragile...
if re.match('^(:?0|-?[1-9][0-9]*)$', s):
    i = int(s)
else:
    print "Not an integer! Please try again."
    sys.exit(1)

And LBYL is sometimes outright incorrect:

if os.path.isfile('some_file'):
    # At this point, some other program could
    # delete some_file...
    f = open('some_file', 'r')

If you want to write a LBYL function for importing optional modules, be my guest... it sounds like the function would be a total monster.

The getattr way

If you just need a default value, getattr is a shorter version of try/except.

x = getattr(self, 'x', default_value)

If the default value is expensive to construct, then you'll end up with something like this:

x = getattr(self, 'attr', None)
if x is None:
    x = CreateDefaultValue()
    self.attr = x

Or if None is a possible value,

sentinel = object()

x = getattr(self, 'attr', sentinel)
if x is sentinel:
    x = CreateDefaultValue()
    self.attr = x

Conclusion

Internally, the getattr and hasattr builtins just use try/except technique (except written in C). So they all behave the same way where it counts, and picking the right one is due to a matter of circumstances and style.

The try/except EAFP code will always rub some programmers the wrong way, and the hasattr/getattr LBYL code will irk other programmers. They're both correct, and there's often no truly compelling reason to pick one or the other. (Yet other programmers are disgusted that you would consider it normal for an attribute to be undefined, and some programmers are horrified that it's even possible to have an undefined attribute in Python.)

Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • 4
    Arguably the `try... except` is better when you nearly always expect `x` to have attribute `x.attr`. (Then the exception would be truly exceptional.) – Li-aung Yip Mar 17 '12 at 09:26
  • 3
    I almost always use `try... except` personally, the `hasattr` method leaves a bad taste in my mouth. – Dietrich Epp Mar 17 '12 at 09:27
  • @Dietrich Epp: code that might hide an exception such as `try/except` above is definitely worse than `hasattr()`. – jfs Mar 17 '12 at 09:45
  • @J.F.Sebastian: Normally it's a `try/except/else` block. You have my blessing to edit it in if you feel it's important. – Dietrich Epp Mar 17 '12 at 09:51
  • However, using `getattr` with a default is certainly better than coding that logic yourself with a `try/except` block. – Karl Knechtel Mar 17 '12 at 13:07
  • @KarlKnechtel: The question was how to check to see if an attribute exists, not how to get an attribute and fall back to a default if the attribute is not set. Do you care to explain why your method is better? – Dietrich Epp Mar 17 '12 at 14:37
  • @DietrichEpp the normal reason for checking for the existence of an attribute is to use it for something if it does, and take some other action otherwise. In cases where "some other action" can be reduced to "use some other value for the same 'something'", there's no point in reinventing the wheel. – Karl Knechtel Mar 17 '12 at 15:27
  • @KarlKnechtel: If you take a look at other people's Python code, you'll see the `try/except AttributeError` idiom all over the place, it's hardly reinventing the wheel (the "Zen of Python" recommends `try/except`). Sure, `getattr` has its place, but if your default value is nontrivial to calculate you're no better off. It's misleading to call it "certainly better" without qualification, and then state in a followup that you're only talking about one specific use case. Because there are legitimate reasons to use both methods. – Dietrich Epp Mar 17 '12 at 17:14
  • But I **did** qualify. "that logic" refers to the logic that you get by using `getattr`. Obviously, if you're going to handle the `except` case by doing something completely different, then `getattr` doesn't accomplish the same task. Anyway, if something is nontrivial to calculate, and isn't precalculated, then I wouldn't be comfortable calling it a "default value". – Karl Knechtel Mar 17 '12 at 17:17
  • 1
    @KarlKnechtel: Ah, of course. Apologies. I think 9 out of 10 times I misinterpret a comment on this site it's due to those tricky pronouns and their antecedents; their interpretation is neither obvious nor unambiguous and two people will long exchange words without actually communicating. – Dietrich Epp Mar 17 '12 at 18:24
  • There is a chance to catch a wrong `AttributeError` exception that is plausible in your code if you call some other functions in your `try` statement. – mlt Mar 06 '14 at 01:35
  • `try` and `hasattr` are much different in terms of time, so it depends on what is the most frequent case: the attribute exists or not. See the analysis here: http://stackoverflow.com/questions/903130/hasattr-vs-try-except-block-to-deal-with-non-existent-attributes – 0 _ Sep 04 '14 at 22:30
  • This is a very extensive and detailed answer. Would you mind cross-posting to [*How to know if an object has an attribute in Python*](https://stackoverflow.com/q/610883/3357935)? – Stevoisiak Mar 27 '18 at 13:59
  • `>>> 'prop' in dir(a) True` – Wyrmwood Aug 19 '20 at 16:02
  • one of the most valuable and well written posts. Had to say thanx – hussam Oct 17 '21 at 02:00
12

hasattr() is the way*.

a.__dict__ is ugly and it doesn't work in many cases. hasattr() actually tries to get attribute and catches AttributeError internally so it works even if you define custom __getattr__() method.

To avoid requesting the attribute twice the third argument for getattr() could be used:

not_exist = object()

# ...
attr = getattr(obj, 'attr', not_exist)
if attr is not_exist:
   do_something_else()
else:
   do_something(attr)

You could just use a default value instead of not_exist sentinel if it is more appropriate in your case.

I don't like try: do_something(x.attr) \n except AttributeError: .. it might hide AttributeError inside do_something() function.

*Before Python 3.1 hasattr() suppressed all exceptions (not only AttributeError) if it is not desirable getattr() should be used.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
9

hasattr() is the Pythonic way to do it. Learn it, love it.

Other possible way is to check whether the variable name is in locals() or globals():

if varName in locals() or in globals():
    do_something()
else:
    do_something_else()

I personally hate to catch exceptions in order to check something. It looks and feels ugly. It's identical to checking if a string contains only digits that way:

s = "84984x"
try:
    int(s)
    do_something(s)
except ValueError:
    do_something_else(s)

Instead of gently using s.isdigit(). Eww.

Naquarus
  • 35
  • 1
  • 8
iTayb
  • 12,373
  • 24
  • 81
  • 135
  • The `int`/`isdigit` versions differ in an important way; the `int` version allows negative numbers. I challenge you to come up with a concise and readable equivalent to `try: int(s) except ValueError: ...` that works correctly for negative numbers, and rejects numbers with extraneous leading zeroes (as Python does). – Dietrich Epp Mar 17 '12 at 18:30
0

Very old question but it really needs a good answer. For even a short program, I'd say use a custom function!

Here's an example. It's not perfect for all application but it is for mine, for parsing responses from countless APIs and using Django. It's easy to fix for everyone's own requirements.

from django.core.exceptions import ObjectDoesNotExist
from functools import reduce

class MultipleObjectsReturned(Exception):
    pass

def get_attr(obj, attr, default, asString=False, silent=True):
    """
    Gets any attribute of obj.
    Recursively get attributes by separating attribute names with the .-character.        
    Calls the last attribute if it's a function.

    Usage: get_attr(obj, 'x.y.z', None)
    """
    try:
        attr = reduce(getattr, attr.split("."), obj)
        if hasattr(attr, '__call__'):
            attr = attr()
        if attr is None:
            return default
        if isinstance(attr, list):
            if len(attr) > 1:
                logger.debug("Found multiple attributes: " + str(attr))
                raise MultipleObjectsReturned("Expected a single attribute")
            else:
                return str(attr[0]) if asString else attr[0]
        else:
            return str(attr) if asString else attr
    except AttributeError:
        if not silent:
            raise
        return default
    except ObjectDoesNotExist:
        if not silent:
            raise
        return default
Lindlof
  • 2,152
  • 2
  • 17
  • 26