2014-01-08 7 views
9

При исследовании для another question, я обнаружил следующее:Аксессуар метода Python создает новые объекты для каждого доступа?

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

Это Ожидалось:

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

Но я не ожидать:

>>> a.m is a.m 
False 

и особенно не это :

>>> A.m is A.m 
False 

Возможно, Python создает новые объекты для каждого доступа к методу. Почему я вижу это поведение? То есть в чем причина, почему он не может повторно использовать один объект для каждого класса и один экземпляр?

ответ

14

Да, 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.

+0

Я вижу, и это было на самом деле то, что я изучал. Однако я ожидал, что он будет хотя бы повторно использовать объект для каждого статического вызова. – Krumelur

+0

@ Krumelur: Почему? Python - динамический язык, простой акт доступа к 'a.m' мог бы вызвать код, который * заменен *' m' для следующего доступа. –

+0

Когда это делается, это имеет большой смысл. Но это противоречило мне. – Krumelur

7

Потому что это самый удобный, наименее волшебный и наиболее эффективный способ использования связанных методов.

В случае, если вы не знаете, связаны методы относится к возможности сделать что-то вроде этого:

f = obj.m 
# ... in another place, at another time 
f(args, but, not, self) 

Функции дескрипторы. Дескрипторы - это общие объекты, которые могут вести себя по-разному при доступе в качестве атрибута класса или объекта. Они используются для реализации property, classmethod, staticmethod и еще нескольких вещей. Конкретная работа дескрипторов функций заключается в том, что они возвращаются для доступа к классу и возвращают объект с привязкой свежего объекта, например, к доступу.(На самом деле это справедливо только для Python 3, Python 2 более сложный в этом отношении, он имеет «несвязанные методы», которые в основном являются функциями, но не совсем).

Причина, по которой новый объект создается при каждом доступе, является простотой и эффективностью. Создание связанного метода для каждого метода каждого экземпляра требует времени и пространства. Создание их по требованию и никогда их не освобождение - это потенциальная утечка памяти (хотя CPython делает что-то подобное для других встроенных типов) и в некоторых случаях немного медленнее. Сложные объекты метода схем кэширования на основе слабого доступа не являются свободными и значительно более сложными (исторически, связанные методы предшествуют слабой стороне).

+0

Это определенно имеет смысл, и я ожидал ответа в этом направлении. Я бы ожидал, что вызовы методов должны быть очень быстрыми, а создание объектов будет слишком медленным. Но тогда производительность итеративного вызова вызова не является сильной стороной Python. – Krumelur

+0

И мне особенно нравится «наименее волшебный». Желаю, чтобы другие чаще рассматривали это решение :) – Krumelur

+0

@ Krumelur Вы правы, вызовы методов медленнее, чем в большинстве языков, но только частично из-за связанных объектов метода. И поиск атрибутов, и обычные вызовы функций уже (сравнительно) дороги, и по крайней мере CPython и PyPy «кеш метода» уменьшают стоимость. – delnan

Смежные вопросы