Ответ 1
Всякий раз, когда вы просматриваете метод через instance.name
(и в Python 2, class.name
), объект метода создается a-new. Python использует протокол дескриптора, чтобы каждый раз обматывать функцию в объекте метода.
Итак, когда вы просматриваете id(C.foo)
, создается новый объект метода, вы извлекаете его идентификатор (адрес памяти), а затем снова отбрасываете объект метода. Затем вы просматриваете id(cobj.foo)
, новый объект метода, созданный, который повторно использует освобожденный адрес памяти, и вы видите одно и то же значение. Затем метод снова отбрасывается (мусор, собранный, когда счетчик ссылок падает до 0).
Затем вы сохранили ссылку на C.foo
метод C.foo
в переменной. Теперь адрес памяти не освобожден (счетчик ссылок равен 1, а не 0), и вы создаете второй экземпляр метода, просматривая cobj.foo
который должен использовать новое местоположение памяти. Таким образом, вы получаете два разных значения.
Верните "идентификатор" объекта. Это целое число (или длинное целое число), которое гарантировано будет уникальным и постоянным для этого объекта в течение его жизни. Два объекта с неперекрывающимся временем жизни могут иметь одинаковое значение
id()
.Подробности реализации CPython: это адрес объекта в памяти.
Акцент мой.
Вы можете повторно создать метод, используя прямую ссылку на функцию через атрибут __dict__
класса, а затем вызов __get__
дескриптора __get__
:
>>> class C(object):
... def foo(self):
... pass
...
>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x1088cc488>
>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>
>>> C.__dict__['foo'].__get__(C(), C)
<bound method C.foo of <__main__.C object at 0x1088d6f90>>
Обратите внимание, что в Python 3 все отличия метода unbound/bound были отброшены; вы получаете функцию, где до того, как вы получите несвязанный метод, и метод в противном случае, когда метод всегда связан:
>>> C.foo
<function C.foo at 0x10bc48dd0>
>>> C.foo.__get__(None, C)
<function C.foo at 0x10bc48dd0>
>>> C.foo.__get__(C(), C)
<bound method C.foo of <__main__.C object at 0x10bc65150>>
Кроме того, в Python 3.7 добавлена новая LOAD_METHOD
CALL_METHOD
операций LOAD_METHOD
- CALL_METHOD
которая заменяет текущую LOAD_ATTRIBUTE
CALL_FUNCTION
операции LOAD_ATTRIBUTE
- CALL_FUNCTION
чтобы избежать создания нового объекта метода каждый раз. Эта оптимизация преобразует путь выполнения для instance.foo()
из type(instance).__dict__['foo'].__get__(instance, type(instance))()
с type(instance).__dict__['foo'](instance)
, поэтому "вручную" передается в экземпляр непосредственно объекту функции.