Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the statement (foo.__init__ is foo.__init__) return false [duplicate]

When investigating for another question, I found the following:

>>> class A:
...   def m(self): return 42
... 
>>> a = A()

This was expected:

>>> A.m == A.m
True
>>> a.m == a.m
True

But this I did not expect:

>>> a.m is a.m
False

And especially not this:

>>> A.m is A.m
False

Python seems to create new objects for each method access. Why am I seeing this behavior? I.e. what is the reason why it can't reuse one object per class and one per instance?

like image 228
Krumelur Avatar asked Jan 08 '14 17:01

Krumelur


People also ask

What does __ init __ return?

__init__ doesn't return anything and should always return None .

Why is __ init __ method used?

The __init__ method is the Python equivalent of the C++ constructor in an object-oriented approach. The __init__ function is called every time an object is created from a class. The __init__ method lets the class initialize the object's attributes and serves no other purpose. It is only used within classes.

Is the __ init __ method technically a constructor in Python true or false and why or why not?

"__init__" is a reseved method in python classes. It is known as a constructor in object oriented concepts. This method called when an object is created from the class and it allow the class to initialize the attributes of a class.

What does __ init __ mean?

"__init__" is a reseved method in python classes. It is called as a constructor in object oriented terminology. This method is called when an object is created from a class and it allows the class to initialize the attributes of the class.


1 Answers

Yes, Python creates new method objects for each access, because it builds a wrapper object to pass in self. This is called a bound method.

Python uses descriptors to do this; function objects have a __get__ method that is called when accessed on a class:

>>> A.__dict__['m'].__get__(A(), A)
<bound method A.m of <__main__.A object at 0x10c29bc10>>
>>> A().m
<bound method A.m of <__main__.A object at 0x10c3af450>>

Note that Python cannot reuse A().m; Python is a highly dynamic language and the very act of accessing .m could trigger more code, which could alter behaviour of what A().m would return next time when accessed.

The @classmethod and @staticmethod decorators make use of this mechanism to return a method object bound to the class instead, and a plain unbound function, respectively:

>>> class Foo:
...     @classmethod
...     def bar(cls): pass
...     @staticmethod
...     def baz(): pass
... 
>>> Foo.__dict__['bar'].__get__(Foo(), Foo)
<bound method type.bar of <class '__main__.Foo'>>
>>> Foo.__dict__['baz'].__get__(Foo(), Foo)
<function Foo.baz at 0x10c2a1f80>
>>> Foo().bar
<bound method type.bar of <class '__main__.Foo'>>
>>> Foo().baz
<function Foo.baz at 0x10c2a1f80>

See the Python descriptor howto for more detail.

However, 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. The optimisation falls back to the normal attribute access path (including binding descriptors) if the attribute found is not a pure-python function object.

like image 111
Martijn Pieters Avatar answered Nov 15 '22 09:11

Martijn Pieters