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
Другие вопросы по тегам