¿Cuál es la diferencia entre las variables de clase y de instancia?

¿Cuál es la diferencia entre las variables de clase e instancia en Python?

class Complex: a = 1 

y

 class Complex: def __init__(self): self.a = 1 

Usando la llamada: x = Complex().a en ambos casos asigna x a 1.

Una respuesta más profunda sobre __init__() y self será apreciada.

Cuando escribe un bloque de clase, crea atributos de clase (o variables de clase). Todos los nombres que asigne en el bloque de clase, incluidos los métodos que defina con def convierten en atributos de clase.

Después de crear una instancia de clase, cualquier cosa con una referencia a la instancia puede crear atributos de instancia en ella. Dentro de los métodos, la instancia “actual” casi siempre está vinculada al nombre self , razón por la cual estás pensando en estos como “auto variables”. Por lo general, en el diseño orientado a objetos, se supone que el código adjunto a una clase tiene control sobre los atributos de las instancias de esa clase, por lo que casi toda la asignación de atributos de la instancia se realiza dentro de los métodos, utilizando la referencia a la instancia recibida en el parámetro self el método.

Los atributos de clase a menudo se comparan con las variables (o métodos) estáticas que se encuentran en lenguajes como Java, C # o C ++. Sin embargo, si desea buscar una comprensión más profunda, evitaría pensar en los atributos de clase como “lo mismo” que las variables estáticas. Mientras que a menudo se usan para los mismos propósitos, el concepto subyacente es bastante diferente. Más sobre esto en la sección “avanzada” debajo de la línea.

¡Un ejemplo!

 class SomeClass: def __init__(self): self.foo = 'I am an instance attribute called foo' self.foo_list = [] bar = 'I am a class attribute called bar' bar_list = [] 

Después de ejecutar este bloque, hay una clase SomeClass , con 3 atributos de clase: __init__ , bar y bar_list .

Luego crearemos una instancia:

 instance = SomeClass() 

Cuando esto sucede, se SomeClass el método __init__ , recibiendo la nueva instancia en su parámetro self . Este método crea dos atributos de instancia: foo y foo_list . Luego, esta instancia se asigna a la variable de instance , por lo que está vinculada a una cosa con esos dos atributos de instancia: foo y foo_list .

Pero:

 print instance.bar 

da:

 I am a class attribute called bar 

¿Cómo pasó esto? Cuando intentamos recuperar un atributo a través de la syntax de puntos y el atributo no existe, Python realiza una serie de pasos para intentar cumplir con su solicitud de todos modos. Lo siguiente que intentará es observar los atributos de clase de la clase de su instancia. En este caso, encontró una bar atributos en SomeClass , por lo que devolvió eso.

Así es como funcionan las llamadas a los métodos, por cierto. Cuando llama a mylist.append(5) , por ejemplo, mylist no tiene un atributo llamado append . Pero la clase de mylist hace, y está vinculada a un objeto de método. Ese objeto de método es devuelto por el bit mylist.append , y luego el bit (5) llama al método con el argumento 5 .

La forma en que esto es útil es que todas las instancias de SomeClass tendrán acceso al mismo atributo de bar . Podríamos crear un millón de instancias, pero solo necesitamos almacenar esa cadena en la memoria, porque todos pueden encontrarla.

Pero hay que tener un poco de cuidado. Echa un vistazo a las siguientes operaciones:

 sc1 = SomeClass() sc1.foo_list.append(1) sc1.bar_list.append(2) sc2 = SomeClass() sc2.foo_list.append(10) sc2.bar_list.append(20) print sc1.foo_list print sc1.bar_list print sc2.foo_list print sc2.bar_list 

¿Qué crees que esto imprime?

 [1] [2, 20] [10] [2, 20] 

Esto se debe a que cada instancia tiene su propia copia de foo_list , por lo que se foo_list por separado. Pero todas las instancias comparten acceso a la misma bar_list . Entonces, cuando hicimos sc1.bar_list.append(2) , afectó a sc2 , ¡a pesar de que sc2 aún no existía! Asimismo, sc2.bar_list.append(20) afectó a bar_list recuperado a través de sc1 . Esto a menudo no es lo que quieres.


El estudio avanzado sigue. 🙂

Para realmente asimilar Python, provenientes de lenguajes OO tipificados estáticamente como Java y C #, tienes que aprender a repensar las clases un poco.

En Java, una clase no es realmente una cosa por derecho propio. Cuando escribes una clase estás más declarando un montón de cosas que todas las instancias de esa clase tienen en común. En el tiempo de ejecución, solo hay instancias (y métodos / variables estáticas, pero en realidad son solo variables y funciones globales en un espacio de nombres asociado con una clase, nada que ver con OO en realidad). Las clases son la forma en que escribes en tu código fuente cómo serán las instancias en el tiempo de ejecución; solo “existen” en su código fuente, no en el progtwig en ejecución.

En Python, una clase no es nada especial. Es un objeto como cualquier otra cosa. Así que “atributos de clase” son de hecho exactamente lo mismo que “atributos de instancia”; en realidad solo hay “atributos”. La única razón para hacer una distinción es que tendemos a usar objetos que son clases diferentes de objetos que no son clases. La maquinaria subyacente es la misma. Por eso digo que sería un error pensar en los atributos de clase como variables estáticas de otros idiomas.

¡Pero lo que realmente hace que las clases de Python sean diferentes de las de estilo Java es que, al igual que con cualquier otro objeto, cada clase es una instancia de alguna clase !

En Python, la mayoría de las clases son instancias de una clase incorporada llamada type . Es esta clase la que controla el comportamiento común de las clases y hace que todas las cosas de OO sean como lo hacen. La forma predeterminada de OO de tener instancias de clases que tienen sus propios atributos, y tienen métodos / atributos comunes definidos por su clase, es solo un protocolo en Python. Puedes cambiar la mayoría de los aspectos si quieres. Si alguna vez has oído hablar de usar una metaclase , todo eso es definir una clase que es una instancia de una clase diferente a la de type .

La única cosa realmente “especial” acerca de las clases (aparte de toda la maquinaria integrada para hacer que funcionen como lo hacen por defecto), es la syntax del bloque de clase, para facilitar la creación de instancias de type . Esta:

 class Foo(BaseFoo): def __init__(self, foo): self.foo = foo z = 28 

es aproximadamente equivalente a lo siguiente:

 def __init__(self, foo): self.foo = foo classdict = {'__init__': __init__, 'z': 28 } Foo = type('Foo', (BaseFoo,) classdict) 

Y organizará que todos los contenidos de classdict conviertan en atributos del objeto que se crea.

Entonces, se vuelve casi trivial ver que puede acceder a un atributo de clase por Class.attribute tan fácilmente como i = Class(); i.attribute i = Class(); i.attribute . Tanto i como Class son objetos, y los objetos tienen atributos. Esto también facilita la comprensión de cómo puede modificar una clase después de que se haya creado; simplemente asigna sus atributos de la misma forma que lo harías con cualquier otro objeto!

De hecho, los casos no tienen una relación especial particular con la clase utilizada para crearlos. La forma en que Python sabe en qué clase buscar atributos que no se encuentran en la instancia es mediante el atributo __class__ oculto. Que puedes leer para averiguar de qué clase es una instancia, al igual que con cualquier otro atributo: c = some_instance.__class__ . Ahora tienes una variable c vinculada a una clase, aunque probablemente no tenga el mismo nombre que la clase. Puede usar esto para acceder a los atributos de la clase, o incluso llamarlo para crear más instancias de él (¡aunque no sepa qué clase es!).

¡Y hasta puedes asignar a i.__class__ para cambiar de qué clase es una instancia! Si haces esto, nada en particular sucede inmediatamente. No es conmovedor. Todo lo que significa es que cuando busque atributos que no existen en la instancia, Python irá a ver los nuevos contenidos de __class__ . Como eso incluye la mayoría de los métodos, y los métodos generalmente esperan que la instancia en la que están operando se encuentre en ciertos estados, esto generalmente genera errores si lo hace al azar, y es muy confuso, pero puede hacerse. Si tiene mucho cuidado, lo que almacena en __class__ ni siquiera tiene que ser un objeto de clase; todo lo que Python va a hacer con él es buscar atributos en ciertas circunstancias, de modo que todo lo que necesita es un objeto que tenga el tipo correcto de atributos (algunas advertencias a un lado donde Python se vuelve delicado en cuanto a las clases o instancias de una clase en particular).

Probablemente sea suficiente por ahora. Con suerte (si has leído hasta aquí) no te he confundido demasiado. Python está limpio cuando aprendes cómo funciona. 🙂

Lo que estás llamando una variable de “instancia” no es en realidad una variable de instancia; Es una variable de clase . Ver la referencia del lenguaje sobre las clases .

En su ejemplo, la a parece ser una variable de instancia porque es inmutable. Su naturaleza como una variable de clase se puede ver en el caso cuando asigna un objeto mutable:

 >>> class Complex: >>> a = [] >>> >>> b = Complex() >>> c = Complex() >>> >>> # What do they look like? >>> ba [] >>> ca [] >>> >>> # Change b... >>> baappend('Hello') >>> ba ['Hello'] >>> # What does c look like? >>> ca ['Hello'] 

Si usas self , entonces sería una verdadera variable de instancia, y así cada instancia tendrá su propia y única. La función __init__ un objeto se llama cuando se crea una nueva instancia , y self es una referencia a esa instancia.