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?
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>
I restructured your example a little to better illustrate the variants I'll use to explain the behaviour
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:
#...
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With