Don_Chen
Don_Chen

Reputation: 997

Why hasattr return a wrong result?

Below is my test code:

import functools


class LazyLoader(object):

    def __init__(self, klass, *args, **kwargs):
        self.klass = klass
        self.args = args
        self.kwargs = kwargs
        self.instance = None

    def __getattr__(self, name):
        return functools.partial(self.__run_method, name)

    def __run_method(self, __name, *args, **kwargs):
        if self.instance is None:
            self.instance = self.klass(*self.args, **self.kwargs)
        return getattr(self.instance, __name)(*args, **kwargs)


class SchedulerReportClient(object):
    def method(self):
        print 'method called'

    def __getattr__(self, name):
        print '__getattr__ called'
        return functools.partial(self.__run_method, name)

    def __run_method(self, __name, *args, **kwargs):
        print '__run_method called'
        return getattr(self, __name)(*args, **kwargs)


if __name__ == "__main__":
    a = SchedulerReportClient()
    entity = LazyLoader(a)

    print hasattr(entity, 'obj_to_primitive')
    print(callable(entity.obj_to_primitive))
    print entity.__class__.__name__
    if hasattr(entity, 'obj_to_primitive') and callable(entity.obj_to_primitive):
        entity.obj_to_primitive()

And the outouts are:

True
True
Traceback (most recent call last):
LazyLoader
  File "C:/Users/chen/PycharmProjects/pytest/main", line 42, in <module>
    entity.obj_to_primitive()
  File "C:/Users/chen/PycharmProjects/pytest/main", line 17, in __run_method
    self.instance = self.klass(*self.args, **self.kwargs)
TypeError: 'SchedulerReportClient' object is not callable

The code is mostly in openstack nova, I copied some.

There isn't a obj_to_primitive method in class SchedulerReportClient, why hasattr and callable function return true?

CentOS7.4 with Python2.7.

Upvotes: 1

Views: 368

Answers (1)

Olivier Melan&#231;on
Olivier Melan&#231;on

Reputation: 22294

The builtin function hasattr is implemented by calling getattr and checking if it raises an exception or not. This is stated in the doc.

The result is True if the string is the name of one of the object’s attributes, False if not. (This is implemented by calling getattr(object, name) and seeing whether it raises an exception or not.)

In this case you defined the LazyLoader.__getattr__ method which returns a partially evaluated method LazyLoader.__run_method regardless of the value of the argument name.

def __getattr__(self, name):
        return functools.partial(self.__run_method, name)

As a reminder, the __getattr__ method is called when an attribute was not found by the usual means.

So when you get entity.obj_to_primitive, what is returned is functools.partial(entity.__run_method, 'obj_to_primitive').

Upvotes: 1

Related Questions