2011-01-09 23 views
12

El Python documentation about the is operator dice:¿Por qué un método no es idéntico a sí mismo?

los operadores is y is not prueba de identidad objeto: x is y es verdadero si y sólo si x y y son el mismo objeto. x is not y produce el valor inverso de verdad .

Vamos a tratar de que:

>>> def m(): 
... pass 
... 
>>> m is m 
True 

El Python documentation also says:

Debido a la recolección de basura automática, listas libres, y la naturaleza dinámica de descriptores, es posible que observe aparentemente comportamiento inusual en ciertos usos de el operador is, como aquellos que implican comp arisons entre métodos de instancia, o constantes. Consulte su documentación para obtener más información.

>>> class C: 
... def m(): 
...  pass 
... 
>>> C.m is C.m 
False 

me han buscado para más explicaciones, pero no fue capaz de encontrar ninguna.

¿Por qué C.m is C.m es falso?

Estoy usando Python 2.x. Como se señala en las respuestas a continuación, en Python 3.x C.m is C.m es verdadero.

Respuesta

17

Cuando solicita un atributo de una instancia que es una función, obtiene un método vinculado: un objeto invocable que ajusta la función definida en la clase y pasa la instancia como primer argumento. En Python 2.x, cuando se pide un atributo de una clase que es una función se obtiene un objeto proxy similar llamado un método no unido :

>>> class A: m = lambda: None 
... 
>>> A.m 
<unbound method A.<lambda>> 

Este objeto especial se crea cuando se la solicita, y no aparentemente en la caché en cualquier lugar. Eso significa que cuando se hace

>>> A.m is A.m 
False 

va a crear dos distintos objetos método no unidos y probándolos para la identidad.

en cuenta que

>>> x = A.m 
>>> x is x 
True 

y

>>> A.m.im_func is A.m.im_func 
True 

trabajo fino. (im_func es la función original que el método objeto no unido está terminando.)

En Python 3.x, dicho sea de paso, C.m is C.m es cierto, porque los (algo sin sentido) objetos proxy método sin unir se eliminaron por completo y que acaba de obtener el original función que definiste


Este es sólo un ejemplo de la naturaleza muy dinámica de búsqueda de atributos en Python: cuando se pide un atributo de un objeto, es posible ejecutar Python arbitrario para calcular el valor de ese atributo. Aquí hay otro ejemplo en el que la prueba falla en la cual es mucho más claro por qué:

>>> class ChangingAttribute(object): 
...  @property 
...  def n(self): 
...    self._n += 1 
...    return self._n 
... 
...  def __init__(self): 
...    self._n = 0 
... 
>>> foo = ChangingAttribute() 
>>> foo.n 
1 
>>> foo.n 
2 
>>> foo.n 
3 
>>> foo.n is foo.n 
False 
>>> foo.n 
6 
+0

Para mayor completitud, [Descriptors] (http://docs.python.org/reference/datamodel.html#invoking-descriptors) realiza esta búsqueda dinámica de atributos. Es lo que difiere el acceso de los atributos a las superclases también. –

+0

Sí, aunque también podría escribir un ejemplo usando '__getattr__'. – katrielalex

4

Debido a Cm() no es un método estático de la clase C:

Trate de esta manera:

class C: 
    @staticmethod 
    def m(): 
     pass 

print C.m is C.m 
# True 

c = C() 
print c.m is C.m 
# True 

Como los métodos estáticos son como variables de clase, solo queremos una referencia para ellos, de modo que si cambiamos su valor enlazado, este cambio debería ser automático en todas las clases e instancias de esta clase.

Por otra parte, en su ejemplo, C.m no es un método estático para Python hace la suposición de que debe ser tratado como un método no estático, por lo que cada vez que llame C.m, devolverá una nueva instancia:

class C: 
    def m(): 
     pass 

a = C.m 
b = C.m 

print id(a), id(b) 
# 43811616, 43355984 
print a is b 
# False 

Nota: ¡los métodos estáticos no son como los métodos de clase!

6

Supongo que está utilizando Python 2? En Python 3, C.m is C.m (pero C().m is C().m sigue siendo falso). Si ingresas solo C.m en el REPL, apuesto a que ves algo como <UnboundMethod... >. Un contenedor UnboundMethod hace muy poco, excepto comprobar isinstance(self, cls). (Parece bastante inútil crear un contenedor para esto? Lo es, por lo que se dejó caer en Python 3 - C.m es solo una función). Se crea una nueva instancia de contenedor a pedido cada vez que se accede al método: C.m crea uno, otro C.m crea otro. Dado que son instancias diferentes, C.m is not C.m.

Muy relacionados están los métodos vinculados, que le permiten hacer f = obj.method; f(*args) pero también causan instance.method is not instance.method. Tras la instanciación, todas las funciones definidas en la clase (léase: todos los métodos, excepto por supuesto los parcheados) se convierten en propiedades de la instancia. Cuando accede a ellos, en su lugar obtiene una instancia nueva de un contenedor (el método vinculado) alrededor de la función normal. Este contenedor recuerda la instancia (self) y cuando se llama con (arg1, arg2, ..., argN) simplemente se los entrega a la función - con self agregado como primer argumento. Por lo general, no se da cuenta porque llama al método de inmediato, pero esto es lo que permite pasar implícitamente self sin tener que recurrir al engaño a nivel de lenguaje.

Consulte the history of Python para obtener más detalles y, bueno, la historia.

Cuestiones relacionadas