15

In Python when I call the hasattr on a @property decorator the hasattr function actually runs the @property code block.

E.g. a class:

class GooglePlusUser(object):

    def __init__(self, master):
        self.master = master

    def get_user_id(self):
        return self.master.google_plus_service.people().get(userId='me').execute()['id']


    @property
    def profile(self):
        # this runs with hasattr
        return self.master.google_plus_service.people().get(userId='me').execute()

Running the following code calls the profile property and actually makes the call:

#Check if the call is an attribute
if not hasattr(google_plus_user, call):
    self.response.out.write('Unknown call')
    return

Why? How can I solve this without making the api call?

djvg
  • 11,722
  • 5
  • 72
  • 103
Jimmy Kane
  • 16,223
  • 11
  • 86
  • 117

3 Answers3

18

hasattr() works by actually retrieving the attribute; if an exception is thrown hasattr() returns False. That's because that is the only reliable way of knowing if an attribute exists, since there are so many dynamic ways to inject attributes on Python objects (__getattr__, __getattribute__, property objects, meta classes, etc.).

From the hasattr() documentation:

This is implemented by calling getattr(object, name) and seeing whether it raises an exception or not.

If you don't want a property to be invoked when doing this, then don't use hasattr. Use vars() (which returns the instance dictionary) or dir() (which gives you a list of names on the class as well). This won't let you discover dynamic attributes handled by __getattr__ or __getattribute__ hooks however.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Went with `dir` and that did the trick for me. Thanks again Martijn. – Jimmy Kane May 10 '15 at 13:21
  • Important to note: if *any* Exception is thrown, it concludes that it has attr not. – Chris Sattinger Nov 02 '18 at 10:05
  • 1
    @felix: only in Python 2, in Python 3 only `AttributeError` will cause `hasattr()` to return `False`. Other exceptions are propagated. – Martijn Pieters Nov 02 '18 at 11:27
  • Better, though still any AttributeError raised anywhere within that method call will do it. I just fixed an issue where there are database calls happening because I mistakenly thought I could do object feature testing using `hasatttr` – Chris Sattinger Nov 02 '18 at 13:11
  • @felix: Yes, any `AttributeError` will do that, because there is no way to distinguish between intentional and accidental attribute errors. – Martijn Pieters Nov 02 '18 at 14:24
  • I use a try/except AttributeError block to avoid calling hasattr. in the try section is call getattr. – Blindfreddy Jan 11 '23 at 18:50
6

hasattr is basically implemented like this (except in C):

def hasattr(obj, attrname):
    try:
        getattr(obj, attname)
    except AttributeError:
        return False
    return True

So in true "easier to ask for forgiveness than permission" (EAFP) fashion, to find out if an object has a given attribute, Python simply tries to get the attribute, and converts failure to a return value of False. Since it's really getting the attribute in the success case, hasattr() can trigger code for property and other descriptors.

To check for an attribute without triggering descriptors, you can write your own hasattr that traverses the object's method resolution order and checks to see whether the name is in each class's __dict__ (or __slots__). Since this isn't attribute access, it won't trigger properties.

Conveniently, Python already has a way to walk the method resolution order and gather the names of attributes from an instance's classes: dir(). A simple way to write such a method, then, would be:

# gingerly test whether an attribute exists, avoiding triggering descriptor code
def gentle_hasattr(obj, name):
    return name in dir(obj) or hasattr(obj, name)

Note that we fall back to using hasattr() if we can't find the desired name in dir(), because dir() won't find dynamic attributes (i.e., where __getattr__ is overridden). Code for these will still be triggered, of course, so if you don't care that you don't find them, you could omit the or clause.

On balance, this is wasteful, since it gets all relevant attribute names when we're interested only in whether a specific one exists, but it'll do in a pinch. I'm not even sure that doing the loop yourself rather than calling dir() would be faster on average, since it'll be in Python rather than in C.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • Didn't believe this at first but it's true. If `attrname` specifies a property which raises `AttributeError` `hasattr` confusingly returns `False`. – c z Jan 11 '18 at 17:00
2

Making the variable as a class variable and then calling hasattr on it did the trick for me.

if not hasattr(google_plus_user, GooglePlusUser.call):
  self.response.out.write('Unknown call')
  return
akansh tayal
  • 949
  • 6
  • 3