Objeto de método vinculado e não vinculado em Python

Eu tentei algum código sobre methods ligados e não acoplados. Quando os chamamos, acho que ambos retornariam objects. Mas quando eu uso id() para obter alguma informação, ela retorna algo que não entendo.

IDE: Eclipse

Plugin: 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 

E a saída é …

 5671672 5671672 5671672 5669368 

Por que # 1 e # 2 retornam o mesmo id? Eles não são objects diferentes? E se nós atribuirmos C.foo e conj.foo a duas variables, # 3 e # 4 retornarão o id diferente.

Eu acho que # 3 e # 4 mostram que eles não são o mesmo object, mas # 1 e # 2 …

Qual é a diferença entre o método id de bound e um método unbound?

Sempre que você procura um método via instance.name (e no Python 2, class.name ), o object de método é criado como novo. O Python usa o protocolo do descritor para envolver a function em um object de método a cada vez.

Portanto, quando você procura id(C.foo) , um novo object de método é criado, você recupera seu id (um endereço de memory) e, em seguida, descarta o object de método novamente . Então você procura id(cobj.foo) , um novo object de método criado que reutiliza o endereço de memory liberado agora e vê o mesmo valor. O método é, então, novamente descartado (lixo coletado à medida que a contagem de referência cai para 0).

Em seguida, você armazenou uma referência ao método C.foo unbound em uma variável. Agora o endereço de memory não é liberado (a contagem de referência é 1, em vez de 0), e você cria uma segunda instância de método procurando por cobj.foo que precisa usar um novo local de memory. Assim você obtém dois valores diferentes.

Veja a documentação para id() :

Retorna a “identidade” de um object. Este é um inteiro (ou inteiro longo) que é garantido para ser exclusivo e constante para este object durante sua vida útil. Dois objects com tempos de vida não sobrepostos podem ter o mesmo valor id() .

Detalhe da implementação do CPython : este é o endereço do object na memory.

Ênfase minha.

Você pode recriar um método usando uma referência direta à function através do atributo __dict__ da class e, em seguida, chamando o método do descritor __get__ :

 >>> class C(object): ... def foo(self): ... pass ... >>> C.foo  >>> C.__dict__['foo']  >>> C.__dict__['foo'].__get__(None, C)  >>> C.__dict__['foo'].__get__(C(), C) > 

Note que no Python 3, toda a distinção do método unbound / bound foi descartada; você obtém uma function onde antes você obtinha um método não acoplado e um método de outra forma, onde um método é sempre ligado:

 >>> C.foo  >>> C.foo.__get__(None, C)  >>> C.foo.__get__(C(), C) > 

Além disso, o Python 3.7 adiciona um novo par opcode CALL_METHODCALL_METHOD que substitui o par LOAD_METHODCALL_METHOD opcode atual precisamente para evitar a criação de um novo object de método a cada vez. Esta otimização transforma o caminho de execução para instance.foo() do type(instance).__dict__['foo'].__get__(instance, type(instance))() com type(instance).__dict__['foo'](instance) , então ‘manualmente’ passando a instância diretamente para o object da function.

Adicionando a resposta muito boa do @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