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.foo
unbound 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.foo
which 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_METHOD
opcode pair that replaces the currentLOAD_ATTRIBUTE
-CALL_FUNCTION
opcode 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.Adding to @Martijn Pieters's very good answer: