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