Does Python really create all bound method for every new instance?
Methods are bound on demand, each time you access one.
Accessing the name of a function invokes the descriptor protocol, which on function objects returns a bound method.
A bound method is a thin wrapper around a function object; it stores a reference to the original function and to the instance. When calling a method object, it in turn passes the call to the function, with instance inserted as a first argument.
Methods are not created when the instance is created, so there is no extra memory required a-priori.
You can re-create the steps manually:
>>> class A:
... def __init__(self, name):
... self.name = name
... def foo(self):
... print(self.name)
...
>>> a = A('One')
>>> a.foo
<bound method A.foo of <__main__.A object at 0x100a27978>>
>>> a.foo.__self__
<__main__.A object at 0x100a27978>
>>> a.foo.__func__
<function A.foo at 0x100a22598>
>>> A.__dict__['foo']
<function A.foo at 0x100a22598>
>>> A.__dict__['foo'].__get__(a, A)
<bound method A.foo of <__main__.A object at 0x100a27978>>
>>> A.__dict__['foo'].__get__(a, A)()
One
It is only the method object that is recreated each time; the underlying function remains stable:
>>> a.foo is a.foo
False
>>> b = A('Two')
>>> b.foo is a.foo
False
>>> b.foo.__func__ is a.foo.__func__
True
This architecture also makes classmethod
, staticmethod
, and property
objects work. You can create your own descriptors, creating a whole host of interesting binding behaviours.
Demystifying checker I made shows, functions are saved inside the main class A
dictionary and shared among instances. But then the last couple lines shows once we bound the methods they are on unique memory address.
class A:
def __init__(self, name):
self.name = name
def foo(self):
print(self.name)
a = A('One')
b = A('Two')
d=a.__dict__
D=A.__dict__
print('dict a:', d)
print('dict A:', D)
print(D['foo']) #<function A.foo at 0x000001AF4258CC80>
# both next __func__ point to the parent dict
print(a.foo.__func__) #<function A.foo at 0x000001AF4258CC80>
print(b.foo.__func__) #<function A.foo at 0x000001AF4258CC80>
print(a.foo)
print(b.foo)
#already bound on unique address
print(a.foo==b.foo)
The full output:
dict a: {'name': 'One'}
dict A: {'__module__': '__main__', '__init__': <function A.__init__ at 0x000001AF425D3730>, 'foo': <function A.foo at 0x000001AF425D3620>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
<function A.foo at 0x000001AF425D3620>
<function A.foo at 0x000001AF425D3620>
<function A.foo at 0x000001AF425D3620>
<bound method A.foo of <__main__.A object at 0x000001AF425E21D0>>
<bound method A.foo of <__main__.A object at 0x000001AF42375C50>>
False