5

Given the following class (with a buggy property) then what is the best foolproof way of checking that the bar property exists?

class Foo(object):
    @property
    def bar(self):
        raise AttributeError('unforeseen attribute error!')

Both hasattr and getattr fail and only dir works:

foo = Foo()

print hasattr(foo, 'bar')
# False

try:
    getattr(foo, 'bar')
    print True
except AttributeError as e:
    print False
# False    

print 'bar' in dir(foo)
# True

The best all round solution I can think of is:

def robust_hasattr(obj, attr):
    return hasattr(obj, attr) or attr in dir(obj)

Is there a better way?

101
  • 8,514
  • 6
  • 43
  • 69
  • Why do you need to check it? Just assume it's there and deal with the error if it isn't; either `try` and `except` if you can recover from it, or let it propagate if you can't. – jonrsharpe May 19 '16 at 08:11
  • 3
    Well, your property does raise `AttributeError`. It is *firmly* trying to signal that the attribute is not there. What is your specific usecase that you *must* detect that this is a property? – Martijn Pieters May 19 '16 at 08:17
  • Also see [What's the difference between hasattr() and 'attribute' in dir()?](https://stackoverflow.com/q/17723569). Using `dir()` **may** not be enough. – Martijn Pieters May 19 '16 at 08:18
  • The actual use case is more complicated - I'm subclassing a class that talks to hardware and may have unexpected bugs (including attribute errors). Within the child class I simply want to check that the property names exist (for interfacing reasons) rather than necessarily execute them. The problem with `hasattr` is that it "fails" by hiding any errors (not just `AttributeError`) in the potentially complex parent class, which makes debugging the actual error difficult. – 101 May 19 '16 at 09:16

2 Answers2

9

If you have a buggy property, fix the bug. If raising AttributeError is a bug, then make the property not do that. Raising that exception is the way to signal that you should not be using that attribute.

Using dir() can be a work-around, but it is not foolproof, as dir() is a debugging aid that can both omit information and can be overridden by the object.__dir__ hook (giving your code another vector to introduce bugs). Then there is the possibility of a buggy object.__getattr__ hook, a buggy object.__getattribute__ hook, or even descriptors on the metaclass, all of which would not be detectable by using dir().

Since you are specifically looking for a property, look for the same attribute on the class of your object:

hasattr(foo, 'bar') or isinstance(getattr(type(foo), 'bar', None), property)

For your specific case, the above returns True:

>>> class Foo(object):
...     @property
...     def bar(self):
...         raise AttributeError('unforeseen attribute error!')
...
>>> foo = Foo()
>>> hasattr(foo, 'bar') or isinstance(getattr(type(foo), 'bar', None), property)
True

because there indeed is such a property object on the class.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Thanks, didn't think of checking against the class itself. Like I said above in my use case I just want to check the namespace more than execute the code, so this is a nice solution. – 101 May 19 '16 at 09:18
3

By the rules of Python, the bar attribute does not exist. An object is considered to have an attribute if an attempt to access the attribute doesn't raise an exception.

If you want to use a different notion of whether an attribute exists, you can implement that notion yourself. For example, to check for the existence of an entry corresponding to bar in the instance dict or one of the class dicts:

for obj in (foo,) + type(foo).__mro__:
    if 'bar' in obj.__dict__:
        print "It's there."
        break
else:
    print "Didn't find it."
user2357112
  • 260,549
  • 28
  • 431
  • 505
  • Why not just use `getattr()` on the class, rather than manually walk the MRO? – Martijn Pieters May 19 '16 at 08:24
  • 1
    @MartijnPieters: Because I was thinking about other descriptor types that might raise an AttributeError for the class, too. It's not necessary for a `property`. – user2357112 May 19 '16 at 08:25
  • If you need to dig that deep, you'd have to test for descriptors on the *metaclass* too! :-) – Martijn Pieters May 19 '16 at 08:26
  • 1
    @MartijnPieters: But descriptors on the metaclass aren't considered for attribute access on the instance, so we mostly don't need to worry about them. We might have to deal with custom descriptors for `__dict__` or `__mro__`, though. – user2357112 May 19 '16 at 08:32
  • Interesting approach :) – 101 May 19 '16 at 09:22
  • `if 'bar' in obj.__dict__:` raises an `AttributeError` when `obj` is a built-in object (built-in objects have no `__dict__` attributes). Do you know a more robust solution? – Géry Ogam Oct 29 '19 at 08:40
  • @Maggyero: Skip the `__dict__` check if the object doesn't have one? What even counts as "robust" depends on what exact condition you're attempting to test for. Depending on what you're trying to test, raising an `AttributeError` for objects with no `__dict__` could be the right behavior. – user2357112 Oct 29 '19 at 08:54
  • I am using your code to reproduce the `object.__getattribute__` method in my own `__getattribute__` method. The `object.__getattribute__` method starts by looking up an attribute in the `__mro__` of a class, and if it founds one, it checks it has a `__get__` method and it is a data descriptor (that is to say it has a `__set__` or `__delete__` method) before calling `__get__`. I implemented this check with `hasattr`, but noticed it fails for attributes whose class defines a `__getattribute__` method. So I found you code and it solved the problem, but I noticed it failed on built-in attributes. – Géry Ogam Oct 29 '19 at 09:13
  • The more robust solution for this case was just to catch the `AttributeError`. – Géry Ogam Oct 29 '19 at 09:16
  • 1
    @Maggyero: If you want to check whether an object is a data descriptor, you should be skipping the instance dict even when there is an instance dict. A `__set__` or `__delete__` in the instance dict is ignored by the descriptor protocol, just like with most cases where the Python internals invoke magic methods. – user2357112 Oct 29 '19 at 09:17
  • By the way, I think you meant `for obj in (foo,) + type(foo).__mro__:` otherwise you get `TypeError: can only concatenate list (not "tuple") to list`. Otherwise great answer! – Géry Ogam Oct 29 '19 at 09:17
  • @Maggyero: Yup, looks like I forgot what type `__mro__` has when writing this answer. – user2357112 Oct 29 '19 at 09:19
  • I realized that since you don’t traverse the instance’s MRO when the instance is a *class*, this code does not work when `foo` is a class and the attribute is defined on a parent. So a more general solution that works for *any* `foo` objects is replacing `for obj in (foo,) + type(foo).__mro__:` with `for obj in (foo.__mro__ if isinstance(foo, type) else (foo,)) + type(foo).__mro__:`. And that is actually what `getattr` does (leaving aside the descriptor protocol) since when `foo` is a class (i.e. a `type` instance) it calls `type.__getattribute__` as it overrides `object.__getattribute__`. – Géry Ogam Apr 16 '21 at 08:50
  • For example with `class A: x = 3` and `class B(A): pass`, `getattr(B, 'x')` returns `3`, like `for obj in (B.__mro__ if isinstance(B, type) else (B,)) + type(B).__mro__: if 'x' in vars(obj): vars(obj)['x']`. But `for obj in (B,) + type(B).__mro__: if 'x' in vars(obj): vars(obj)['x']` returns nothing. – Géry Ogam Apr 16 '21 at 08:58
  • @Maggyero: What counts as "working" depends on what concept of attribute existence you want to use. For most purposes, `hasattr` already works. For the alternative concept of attribute existence proposed in the answer, the code in the answer works (unless something weird overrides `__dict__` or `__mro__`). If you want to include a search through `foo.__mro__` when `foo` is a class, you can do that. (We had a similar conversation back in 2019.) – user2357112 Apr 16 '21 at 09:04
  • 1
    For other types that override `__getattribute__`, this answer could similarly be considered not to work. For example, it won't perform the proxy MRO search a `super` object performs if you use it with a `super` object - plus, as we discussed back in 2019, it assumes the object has an instance dict. – user2357112 Apr 16 '21 at 09:11
  • Yes your code is perfectly fine for the OP’s question. I was just pointing out that since in the end it is a simulation of `getattr` but *skipping the descriptor protocol and assuming instance `__dict__`*, and it aims to be a little more general than merely checking for the existence of a `property` attribute as you said, it might as well support `type` instances since the modification is trivial and does not require extra lines. Handling user-defined `__getattribute__` overrides is another story. Now like `type`, `super` is built-in so that is a good argument against this support, I agree. +1 – Géry Ogam Apr 16 '21 at 09:31