Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Python really create all bound method for every new instance?

I am reading about classes in Python (3.4) and from what I understand it seems that every new object has its own bound methods instances.

class A:

    def __init__(self, name):
        self.name = name

    def foo(self):
        print(self.name)

a = A('One')
b = A('Two')

print(a.foo ==  b.foo)

The output of this is False.

This seems to me as a waste of memory. I thought that internally a.foo and b.foo would point somehow internally to one function in memory: A.foo where self as the class instance will be passed.

I assume this maybe cannot be implemented easily in the language.

Does each new instance contain also new instances of its bound methods?

If so, does not this hurt the performance or make case for creating new objects more cautiously than in other languages where methods are "shared" among objects like in Java?

like image 808
ps-aux Avatar asked May 21 '15 16:05

ps-aux


1 Answers

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.

like image 146
Martijn Pieters Avatar answered Nov 12 '22 02:11

Martijn Pieters