Llamando variable de clase con uno mismo

¿Cómo se me ocurrió este ejemplo interesante (al menos para mí)?

import numpy as np class Something(object): a = np.random.randint(low=0, high=10) def do(self): self.a += 1 print(self.a) if __name__ == '__main__': something = Something() print(something.__str__()) something.do() something2 = Something() print(something2.__str__()) something2.do() something3 = Something() print(something3.__str__()) something3.do() 

Lo anterior imprime lo siguiente en la consola:

 $ python test.py  1  1  1 

Estoy un poco confundido porque asumí (erróneamente) que el valor de a habría aumentado.

Puedo obtener el comportamiento que esperaría si utilizara el decorador @classmethod .

 import numpy as np class Something(object): a = np.random.randint(low=0, high=10) @classmethod def do(cls): cls.a += 1 print(cls.a) if __name__ == '__main__': something = Something() print(something.__str__()) something.do() something2 = Something() print(something2.__str__()) something2.do() something3 = Something() print(something3.__str__()) something3.do() 

Esto imprime correctamente lo siguiente en la consola.

 python test.py  3  4  5 

Ahora, me pregunto en el primer ejemplo, cuando me llamo self.a , ¿a qué estoy accediendo? No es una variable de clase, ya que parece que no puedo cambiar su valor. Tampoco es una variable de instancia, ya que parece ser compartida entre diferentes objetos de la misma clase. ¿Cómo lo llamarías?

¿Es esta una variable de clase que estoy usando de manera incorrecta? Sé el nombre de cls si es una convención, así que quizás esté accediendo realmente a una variable de clase, pero no puedo cambiar su valor porque no he decorado el método con el decorador @classmethod .

¿Es este un tipo de uso ilegítimo de la lengua? Me refiero a algo que es mejor no hacer para evitar introducir un error en una etapa posterior.

Lo que está sucediendo es que el self.a refiere a dos cosas en diferentes momentos.

Cuando no existe una variable de instancia para un nombre, Python buscará el valor en la clase. Por lo tanto, el valor recuperado para self.a será la variable de clase.

Pero cuando se configura un atributo a través de self , Python siempre establecerá una variable de instancia. Así que ahora self.a es una nueva variable de instancia cuyo valor es igual a la variable de clase + 1. Este atributo oculta el atributo de clase, al que ya no se puede acceder por self sino solo a través de la clase.

(Un punto menor, que no tiene nada que ver con la pregunta: nunca debe acceder directamente a los métodos de subrayado doble. En lugar de llamar a something2.__str__() , llamar a str(something2) etc.)

La respuesta de Daniel Roseman explica claramente el problema. Aquí hay algunos puntos adicionales y espero que ayude. Puedes usar type (self) .a en lugar de self.a. También mire las discusiones Python: self vs type (self) y el uso adecuado de las variables de clase y Python: self .__ class__ vs. type (self)

 import numpy as np class Something(object): a = np.random.randint(low=0, high=10) def do(self): type(self).a += 1 print(type(self).a) if __name__ == '__main__': something = Something() print(str(something )) something.do() something2 = Something() print(str(something2)) something2.do() something3 = Something() print(str(something3)) something3.do()