Да, Python создает новые объекты метода для каждого доступа, потому что он строит объект-обертку для передачи в self
. Это называется связанным методом .
Python использует дескрипторы для этого; функциональные объекты имеют __get__
метод, который вызывается при доступе на класс:
>>> 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>>
Обратите внимание, что Python не может повторно A().m
; Python - очень динамичный язык, и сам способ доступа к .m
может вызвать больше кода, что может изменить поведение того, что A().m
вернется в следующий раз при обращении.
@classmethod
В и @staticmethod
декораторы используют этот механизм, чтобы вернуть объект метод, связанный с классом вместо этого, и простой несвязанные функции, соответственно:
>>> 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>
Смотрите Python descriptor howto более подробно.
Однако Python 3.7 добавляет новый LOAD_METHOD
- CALL_METHOD
опкода пару, которая заменяет текущую LOAD_ATTRIBUTE
- CALL_FUNCTION
опкода пару раз, чтобы избежать создания нового объекта метод каждый раз. Эта оптимизация преобразует путь выполнения для instance.foo()
от type(instance).__dict__['foo'].__get__(instance, type(instance))()
с type(instance).__dict__['foo'](instance)
, поэтому «вручную» передается в экземпляр непосредственно объекту функции. Оптимизация возвращается к пути доступа к нормальному атрибуту (включая дескрипторы привязки), если найденный атрибут не является объектом функции pure-python.
Я вижу, и это было на самом деле то, что я изучал. Однако я ожидал, что он будет хотя бы повторно использовать объект для каждого статического вызова. – Krumelur
@ Krumelur: Почему? Python - динамический язык, простой акт доступа к 'a.m' мог бы вызвать код, который * заменен *' m' для следующего доступа. –
Когда это делается, это имеет большой смысл. Но это противоречило мне. – Krumelur