¿Por qué los métodos de diferentes objetos de la misma clase tienen la misma identificación?

En el siguiente código, no entiendo por qué useless_func tiene el mismo id cuando pertenece a dos objetos diferentes.

 class parent(object): @classmethod def a_class_method(cls): print "in class method %s" % cls @staticmethod def a_static_method(): print "static method" def useless_func(self): pass p1, p2 = parent(),parent() id(p1) == id(p2) // False id(p1.useless_func) == id(p2.useless_func) // True 

Esto es lo que creo que está pasando:

  1. Cuando elimina la referencia a p1.useless_func , se p1.useless_func una copia en la memoria. Esta ubicación de memoria es devuelta por id
  2. Como no hay referencias a la copia del método que se acaba de crear, el GC la recupera y la dirección de la memoria vuelve a estar disponible.
  3. Cuando elimina la referencia a p2.useless_func , se p2.useless_func una copia de la misma en la misma dirección de memoria (estaba disponible), la cual recupera utilizando id nuevamente.
  4. La segunda copia es GCd.

Si tuviera que ejecutar un montón de otro código y verificar de nuevo los identificadores de los métodos de instancia, apostaría a que los id serían idénticos entre sí, pero diferentes de la ejecución original.

Además, puede notar que en el ejemplo de David Wolver, tan pronto como se obtiene una referencia duradera a la copia del método, los id vuelven diferentes.

Para confirmar esta teoría, aquí hay una sesión de shell con Jython (el mismo resultado con PyPy), que no utiliza la recolección de basura de recuento de referencias de CPython:

 Jython 2.5.2 (Debian:hg/91332231a448, Jun 3 2012, 09:02:34) [OpenJDK Server VM (Oracle Corporation)] on java1.7.0_21 Type "help", "copyright", "credits" or "license" for more information. >>> class parent(object): ... def m(self): ... pass ... >>> p1, p2 = parent(), parent() >>> id(p1.m) == id(p2.m) False 

¡Esta es una pregunta muy interesante!

Bajo tus condiciones, parecen lo mismo:

 Python 2.7.2 (default, Oct 11 2012, 20:14:37) [GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> class Foo(object): ... def method(self): pass ... >>> a, b = Foo(), Foo() >>> a.method == b.method False >>> id(a.method), id(b.method) (4547151904, 4547151904) 

Sin embargo, tenga en cuenta que una vez que haga algo con ellos, se volverán diferentes:

 >>> a_m = a.method >>> b_m = b.method >>> id(a_m), id(b_m) (4547151*9*04, 4547151*5*04) 

Y luego, cuando se prueban de nuevo, ¡han cambiado de nuevo!

 >>> id(b.method) 4547304416 >>> id(a.method) 4547304416 

Cuando se accede a un método en una instancia, se devuelve una instancia de “método enlazado”. Un método enlazado almacena una referencia tanto a la instancia como al objeto de función del método:

 >>> a_m > >>> a_m.im_func is Foo.__dict__['method'] True >>> a_m.im_self is a True 

(tenga en cuenta que necesito usar Foo.__dict__['method'] , no Foo.method , porque Foo.method producirá un “método Foo.method ” … cuyo propósito se deja como ejercicio para el lector)

El propósito de este objeto de “método enlazado” es hacer que los métodos “se comporten sensiblemente” cuando se transmiten como funciones. Por ejemplo, cuando llamo a la función a_m() , es idéntico a llamar a a.method() , aunque ya no tenemos una referencia explícita a. Contraste este comportamiento con JavaScript (por ejemplo), donde var method = foo.method; method() var method = foo.method; method() no produce el mismo resultado que foo.method() .

¡ASI QUE! Esto nos lleva de nuevo a la pregunta inicial: ¿por qué parece que id(a.method) produce el mismo valor que id(b.method) ? Creo que Asad es correcto: tiene que ver con el recolector de basura con recuento de referencias de Python *: cuando se evalúa la expresión id(a.method) , se asigna un método enlazado, se calcula la ID y se desasigna el método enlazado. Cuando se asigna el siguiente método enlazado, para b.method , se asigna exactamente a la misma ubicación en la memoria, ya que no ha habido ninguna asignación (o ha habido un número equilibrado de) desde el método enlazado para a.method fue asignado Esto significa que a.method parece tener la misma ubicación de memoria que b.method .

Finalmente, esto explica por qué las ubicaciones de la memoria parecen cambiar la segunda vez que se verifican: las otras asignaciones que han tenido lugar entre la primera y la segunda verificación significan que, la segunda vez, se asignan en una ubicación diferente (nota: se vuelven a asignar porque todas las referencias a ellos se perdieron; los métodos enlazados se almacenan en caché †, por lo que acceder al mismo método dos veces devolverá la misma instancia: a_m0 = a.method; a_m1 = a.method; a_m0 is a_m1 => True ).

*: nota de los pedantes: en realidad, esto no tiene nada que ver con el recolector de basura real, que solo existe para tratar con referencias circulares … pero … eso es una historia para otro día.
†: al menos en CPython 2.7; CPython 2.6 no parece almacenar en caché los métodos enlazados, lo que me llevaría a esperar que el comportamiento no esté especificado.