Подтвердить что ты не робот

Объект привязанного и несвязанного метода python

Я пробовал некоторый код о связанных и несвязанных методах. Когда мы их называем, я думаю, что оба они вернут объекты. Но когда я использую id() для получения некоторой информации, он возвращает то, что я не понимаю.

IDE: Eclipse Плагин: 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...

В чем разница между идентификатором связанного метода и несвязанным методом?

4b9b3361

Ответ 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():

Верните "идентификатор" объекта. Это целое число (или длинное целое число), которое гарантировано будет уникальным и постоянным для этого объекта в течение его жизни. Два объекта с неперекрывающимся временем жизни могут иметь одинаковое значение 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), поэтому "вручную" передается в экземпляр непосредственно объекту функции.

Ответ 2

Добавление в @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