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 up id(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 up cobj.foo
which has to use a new memory location. Thus you get two different values.
See the documentation for id()
:
Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same
id()
value.CPython implementation detail: This is the address of the object in memory.
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:
>>> class C(object): ... def foo(self): ... pass ... >>> C.foo <unbound method C.foo> >>> C.__dict__['foo'] <function foo at 0x1088cc488> >>> C.__dict__['foo'].__get__(None, C) <unbound method C.foo> >>> C.__dict__['foo'].__get__(C(), C) <bound method C.foo of <__main__.C object at 0x1088d6f90>>
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:
>>> C.foo <function C.foo at 0x10bc48dd0> >>> C.foo.__get__(None, C) <function C.foo at 0x10bc48dd0> >>> C.foo.__get__(C(), C) <bound method C.foo of <__main__.C object at 0x10bc65150>>
Furthermore, Python 3.7 adds a new LOAD_METHOD
- CALL_METHOD
opcode pair that replaces the current LOAD_ATTRIBUTE
- CALL_FUNCTION
opcode pair precisely to avoid creating a new method object each time. This optimisation transforms the executon path for instance.foo()
from type(instance).__dict__['foo'].__get__(instance, type(instance))()
with type(instance).__dict__['foo'](instance)
, so 'manually' passing in the instance directly to the function object.
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