id()s of bound and unbound method objects --- sometimes the same for different objects, sometimes different for the same object
Я попробовал некоторый код о связанных и несвязанных методах. Когда мы их вызываем, я думаю, что они оба будут возвращать объекты. Но когда я использую id()
для получения некоторой информации он возвращает то, чего я не понимаю.
IDE: Затмение
Плагин: pydev
Class C(object):
def foo(self):
pass
cobj = C()
print id(C.foo) #1
print id(cobj.foo) #2
a = C.foo
b = cobj.foo
print id(a) #3
print id(b) #4
И вывод...
5671672
5671672
5671672
5669368
Почему #1 и #2 возвращают один и тот же идентификатор? Разве они не разные объекты? И если мы назначим C.foo
а также conj.foo
для двух переменных #3 и #4 возвращают разные идентификаторы.
Я думаю, что № 3 и № 4 показывают, что это не один и тот же объект, но № 1 и № 2...
В чем разница между идентификатором связанного метода и несвязанного метода?
2 ответа
Всякий раз, когда вы ищите метод с помощью instance.name
(и в Python 2, class.name
), метод объекта создан a-new. Python использует протокол дескриптора, чтобы каждый раз оборачивать функцию в объект метода.
Итак, когда вы смотрите вверх id(C.foo)
создается новый объект метода, вы извлекаете его идентификатор (адрес памяти), а затем снова удаляете объект метода. Тогда ты смотришь вверх id(cobj.foo)
создан новый объект метода, который повторно использует теперь освобожденный адрес памяти, и вы видите то же значение. Затем этот метод снова отбрасывается (мусор собирается, когда счетчик ссылок падает до 0).
Далее вы сохранили ссылку на C.foo
несвязанный метод в переменной. Теперь адрес памяти не освобожден (счетчик ссылок равен 1, а не 0), и вы создаете второй экземпляр метода, просматривая cobj.foo
который должен использовать новую ячейку памяти. Таким образом, вы получаете два разных значения.
Смотрите документацию дляid()
:
Вернуть "личность" объекта. Это целое число (или длинное целое число), которое гарантированно будет уникальным и постоянным для этого объекта в течение срока его службы. Два объекта с неперекрывающимися временами жизни могут иметь одинаковые
id()
значениеДетали реализации CPython: это адрес объекта в памяти.
Акцент мой.
Вы можете заново создать метод, используя прямую ссылку на функцию через__dict__
атрибут класса, затем вызывая__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 все различие между несвязанным и связанным методом было удалено; вы получаете функцию, в которой раньше вы получили несвязанный метод, и метод в противном случае, где методвсегда связан:
>>> 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_ATTRIBUTE
- CALL_FUNCTION
Опкод пары точно, чтобы избежать создания нового объекта метода каждый раз. Эта оптимизация преобразует путь исполнения для instance.foo()
от type(instance).__dict__['foo'].__get__(instance, type(instance))()
с type(instance).__dict__['foo'](instance)
так что "вручную" передавая экземпляр непосредственно объекту функции.
Добавление к очень хорошему ответу@Martijn Pieters:
In [1]: class C(object):
...: def foo(self):
...: pass
...:
In [2]: c = C()
In [3]: id(c.foo), id(C.foo)
Out[3]: (149751844, 149751844) # so 149751844 is current free memory address
In [4]: a = c.foo # now 149751844 is assigned to a
In [5]: id(a)
Out[5]: 149751844
# now python will allocate some different address to c.foo and C.foo
In [6]: id(c.foo), id(C.foo) # different address used this time, and
Out[6]: (149752284, 149752284) # that address is freed after this step
# now 149752284 is again free, as it was not allocated to any variable
In [7]: b = C.foo # now 149752284 is allocated to b
In [8]: id(b)
Out[8]: 149752284
In [9]: c.foo is C.foo # better use `is` to compare objects, rather than id()
Out[9]: False