Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

getattr vs. inspect.getmembers

Tags:

python

It is possible to grab an object attribute using either getattr(obj, attr) or inspect.getmembers(obj) and then filtering by name:

import inspect

class Foo(object):

    def __init__(self):

        self.a = 100

    def method(self): pass

foo = Foo()
method_by_getattr = getattr(foo, 'method')

foo_members = inspect.getmembers(foo) 
method_by_inspect = [member[1] for member in foo_members 
                        if member[0] == "method"][0]

print (id(method_by_getattr), method_by_getattr, type(method_by_getattr))
print (id(method_by_inspect), method_by_inspect, type(method_by_inspect))

a_by_getattr = getattr(foo, "a")
a_by_inspect = [member[1] for member in foo_members
                        if member[0] == "a"][0]

print (id(a_by_getattr), a_by_getattr, type(a_by_getattr))
print (id(a_by_inspect), a_by_inspect, type(a_by_inspect))

# (38842160L, <bound method Foo.method of <__main__.Foo object at 0x00000000025EF390>>, <type 'instancemethod'>)
# (39673576L, <bound method Foo.method of <__main__.Foo object at 0x00000000025EF390>>, <type 'instancemethod'>)
# (34072832L, 100, <type 'int'>)
# (34072832L, 100, <type 'int'>)

For the 'a' attribute getattr and inspect.getmembers are returning the same object. But for the method 'method' they are returning different objects (as can be seen by the disparate id's).

Why is this so?

like image 466
jomido Avatar asked Oct 11 '12 21:10

jomido


2 Answers

millimoose gets the green check, but I thought I'd add a bit.

tl;dr

Bound method objects are transient. That is, they are created anew each time you grab them.

class Foo(object):
    def bar(object): pass

foo = Foo()
m1 = foo.bar
m2 = foo.bar
print (id(m1))
print (id(m2))

# 38121264
# 38952752

More Detail: Descriptor Protocol

Bound method objects are created anew each time you grab them because the function object stored on the class implements the descriptor protocol.

The bar function is stored in the class dict:

class Foo(object):
    def bar(object): pass

print (Foo.__dict__['bar'])

# <function bar at 0x00000000025F2208>

When a Foo instance attempts to access bar, it doesn't have it. So it looks it up on its class and finds it in the class dict. The function object implements a __get__ method, as per the descriptor protocol. So what is actually called to get the bound method is this:

print(Foo.__dict__['bar'].__get__(foo, Foo))

# <bound method Foo.bar of <__main__.Foo object at 0x00000000025719B0>>

Which is equivalent to:

print (foo.bar)

# <bound method Foo.bar of <__main__.Foo object at 0x00000000025719B0>>

You can get at the original function like so:

print (foo.bar.im_func) # Python 2.x

# <function bar at 0x00000000025F2208>    

print (foo.bar.__func__) # Python 3.x

# <function bar at 0x00000000025F2208>   
like image 98
jomido Avatar answered Oct 12 '22 21:10

jomido


I restructured your example a little to better illustrate the variants I'll use to explain the behaviour

With temporary variables

import inspect

def print_id(obj):
    print "{} => {}".format(id(obj), obj)

def getmember(obj, name):
    #members = dict(inspect.getmembers(obj))
    #return members[name]
    return [member 
            for _name, member in inspect.getmembers(obj) 
            if name == _name][0]

class Foo(object):
    def bar(self): pass

foo = Foo()

m1 = foo.bar
m2 = getattr(foo, 'bar')
m3 = getmember(foo, 'bar')

print_id(m1)
print_id(m2)
print_id(m3)

However, if you inspect objects in a REPL, the basic structure of your code will probably look like this:

Without temporary variables

#...
foo = Foo()

print_id(foo.bar)
print_id(getattr(foo, 'bar'))
print_id(getmember(foo, 'bar'))

The id() function basically returns the memory address of an object. That is, it's not an identity that's unique between all objects created during the whole runtime of a program. It's only unique between all the objects that exist in the process at any given point in time.

The explanation that fits the difference between the two examples is that resolving foo.bar in any of the three ways gives you a new object every time. In the first example, these objects are stored in temporary variables, so all three have to be located at different memory addresses.

In the second example, the bound method object is no longer needed after it's printed out; the Python reference counting GC will free its memory. This means that the next time the bound method object is created, it's a new object that happens to get created at the same memory address as the previous one. This is why it can seem like you're getting the same object multiple times.

That you always get a new bound method object can be shown trivially:

>>> foo.bar == foo.bar
True
>>> foo.bar is foo.bar
False
like image 44
millimoose Avatar answered Oct 12 '22 21:10

millimoose