I’ve tried some code about bound and unbound methods. When we call them, I think both of them would return objects. But when I use id() for getting some information, it returns something I don’t understand.
IDE: Eclipse
Plugin: pydev
Class C(object):
def foo(self):
pass
cobj = C()
print id(C.foo) #1
print id(cobj.foo) #2
a = C.foo
b = cobj.foo
print id(a) #3
print id(b) #4
And the output is…
5671672
5671672
5671672
5669368
Why do #1 and #2 return the same id? Aren’t they different objects? And if we assign C.foo and conj.foo to two variables, #3 and #4 return the different id.
I think #3 and #4 show that they are not the same object, but #1 and #2…
What is the difference between the id of bound method, and an unbound method?
Whenever you look up a method via
instance.name(and in Python 2,class.name), the method object is created a-new. Python uses the descriptor protocol to wrap the function in a method object each time.So, when you look up
id(C.foo), a new method object is created, you retrieve its id (a memory address), then discard the method object again. Then you look upid(cobj.foo), a new method object created that re-uses the now freed memory address and you see the same value. The method is then, again, discarded (garbage collected as the reference count drops to 0).Next, you stored a reference to the
C.foounbound method in a variable. Now the memory address is not freed (the reference count is 1, instead of 0), and you create a second method instance by looking upcobj.foowhich has to use a new memory location. Thus you get two different values.See the documentation for
id():Emphasis mine.
You can re-create a method using a direct reference to the function via the
__dict__attribute of the class, then calling the__get__descriptor method:Note that in Python 3, the whole unbound / bound method distinction has been dropped; you get a function where before you’d get an unbound method, and a method otherwise, where a method is always bound:
Furthermore, Python 3.7 adds a new
LOAD_METHOD–CALL_METHODopcode pair that replaces the currentLOAD_ATTRIBUTE–CALL_FUNCTIONopcode pair precisely to avoid creating a new method object each time. This optimisation transforms the executon path forinstance.foo()fromtype(instance).__dict__['foo'].__get__(instance, type(instance))()withtype(instance).__dict__['foo'](instance), so ‘manually’ passing in the instance directly to the function object.