¿Cómo evitar tener datos de clase compartidos entre instancias?

Lo que quiero es este comportamiento:

class a: list = [] x = a() y = a() x.list.append(1) y.list.append(2) x.list.append(3) y.list.append(4) print(x.list) # prints [1, 3] print(y.list) # prints [2, 4] 

Por supuesto, lo que realmente sucede cuando imprimo es:

 print(x.list) # prints [1, 2, 3, 4] print(y.list) # prints [1, 2, 3, 4] 

Claramente están compartiendo los datos en la clase a . ¿Cómo obtengo instancias separadas para lograr el comportamiento que deseo?

Tu quieres esto:

 class a: def __init__(self): self.list = [] 

La statement de las variables dentro de la statement de clase los convierte en miembros de “clase” y no en miembros de instancia. Declararlos dentro del método __init__ se asegura de que se __init__ una nueva instancia de los miembros junto con cada nueva instancia del objeto, que es el comportamiento que está buscando.

La respuesta aceptada funciona pero un poco más de explicación no duele.

Los atributos de clase no se convierten en atributos de instancia cuando se crea una instancia. Se convierten en atributos de instancia cuando se les asigna un valor.

En el código original no se asigna ningún valor a la list atributos después de la instanciación; por lo que sigue siendo un atributo de clase. La definición de la lista dentro de __init__ funciona porque __init__ se llama después de la __init__ instancias. Alternativamente, este código también produciría la salida deseada:

 >>> class a: list = [] >>> y = a() >>> x = a() >>> x.list = [] >>> y.list = [] >>> x.list.append(1) >>> y.list.append(2) >>> x.list.append(3) >>> y.list.append(4) >>> print(x.list) [1, 3] >>> print(y.list) [2, 4] 

Sin embargo, el escenario confuso en la pregunta nunca sucederá con objetos inmutables, como números y cadenas, porque su valor no puede cambiarse sin asignación. Por ejemplo, un código similar al original con tipo de atributo de cadena funciona sin ningún problema:

 >>> class a: string = '' >>> x = a() >>> y = a() >>> x.string += 'x' >>> y.string += 'y' >>> x.string 'x' >>> y.string 'y' 

Entonces, para resumir: los atributos de clase se convierten en atributos de instancia si y solo si se les asigna un valor después de la __init__ instancias, estando en el método __init__ o no . Esto es bueno porque de esta manera puede tener atributos estáticos si nunca asigna un valor a un atributo después de la instanciación.

Usted declaró “lista” como “propiedad de nivel de clase” y no como “propiedad de nivel de instancia”. Para tener propiedades con ámbito en el nivel de instancia, debe inicializarlas a través de referencias con el parámetro “self” en el método __init__ (o en otro lugar, según la situación).

No tiene que estrictamente iniciar las propiedades de la instancia en el método __init__ , pero facilita la comprensión.

Aunque la respuesta aceptada es acertada, me gustaría agregar una pequeña descripción.

Hagamos un pequeño ejercicio

En primer lugar, defina una clase de la siguiente manera:

 class A: temp = 'Skyharbor' def __init__(self, x): self.x = x def change(self, y): self.temp = y 

entonces que tenemos aqui?

  • Tenemos una clase muy simple que tiene un atributo temp que es una cadena
  • Un método __init__ que establece self.x
  • Un método de cambio establece self.temp

Bastante sencillo hasta ahora sí? Ahora vamos a empezar a jugar con esta clase. Vamos a inicializar esta clase primero:

 a = A('Tesseract') 

Ahora haz lo siguiente:

 >>> print(a.temp) Skyharbor >>> print(A.temp) Skyharbor 

Bueno, a.temp funcionó como se esperaba pero, ¿cómo diablos funcionó A.temp ? Bien funcionó porque la temperatura es un atributo de clase. Todo en python es un objeto. Aquí A es también un objeto de type de clase. Por lo tanto, el atributo temp es un atributo mantenido por la clase A y si cambia el valor de temp a través de A (y no a través de una instancia de a ), el valor cambiado se reflejará en todas las instancias de la clase A Sigamos adelante y hagamos eso:

 >>> A.temp = 'Monuments' >>> print(A.temp) Monuments >>> print(a.temp) Monuments 

Interesante ¿no es así? Y tenga en cuenta que id(a.temp) e id(A.temp) siguen siendo los mismos .

A cualquier objeto Python se le __dict__ automáticamente un atributo __dict__ , que contiene su lista de atributos. Investiguemos qué contiene este diccionario para nuestros objetos de ejemplo:

 >>> print(A.__dict__) { 'change': , '__module__': '__main__', '__init__': , 'temp': 'Monuments', '__doc__': None } >>> print(a.__dict__) {x: 'Tesseract'} 

Tenga en cuenta que el atributo temp está listado entre los atributos de la clase A mientras que x está listado para la instancia.

Entonces, ¿cómo es que obtenemos un valor definido de a.temp si ni siquiera está listado para la instancia a . Bueno, esa es la magia del __getattribute__() . En Python, la syntax de puntos invoca automáticamente este método, de modo que cuando escribimos a.temp , Python ejecuta a.__getattribute__('temp') . Ese método realiza la acción de búsqueda de atributos, es decir, encuentra el valor del atributo buscando en diferentes lugares.

La implementación estándar de __getattribute__() busca primero el diccionario interno ( dict ) de un objeto, luego el tipo del objeto en sí. En este caso, a.__getattribute__('temp') ejecuta primero a.__dict__['temp'] y luego a.__class__.__dict__['temp']

Bien, ahora vamos a usar nuestro método de change :

 >>> a.change('Intervals') >>> print(a.temp) Intervals >>> print(A.temp) Monuments 

Bueno, ahora que hemos utilizado self , print(a.temp) nos da un valor diferente de print(A.temp) .

Ahora, si comparamos id(a.temp) e id(A.temp) , serán diferentes.

Sí, debe declarar en el “constructor” si desea que la lista se convierta en una propiedad de objeto y no en una propiedad de clase.

Así que casi todas las respuestas aquí parecen pasar por alto un punto en particular. Las variables de clase nunca se convierten en variables de instancia como lo demuestra el código a continuación. Al utilizar una metaclase para interceptar la asignación de variables en el nivel de clase, podemos ver que cuando se reasigna a.myattr, no se llama al método mágico de asignación de campos en la clase. Esto se debe a que la asignación crea una nueva variable de instancia . Este comportamiento no tiene absolutamente nada que ver con la variable de clase como lo demuestra la segunda clase que no tiene variables de clase y aún así permite la asignación de campos.

 class mymeta(type): def __init__(cls, name, bases, d): pass def __setattr__(cls, attr, value): print("setting " + attr) super(mymeta, cls).__setattr__(attr, value) class myclass(object): __metaclass__ = mymeta myattr = [] a = myclass() a.myattr = [] #NOTHING IS PRINTED myclass.myattr = [5] #change is printed here b = myclass() print(b.myattr) #pass through lookup on the base class class expando(object): pass a = expando() a.random = 5 #no class variable required print(a.random) #but it still works 

EN CORTO Las variables de clase NO tienen NADA que ver con las variables de instancia.

Más claramente , simplemente están en el ámbito de las búsquedas en instancias. Las variables de clase son de hecho variables de instancia en el objeto de clase en sí. También puede tener variables de metaclase si así lo desea, porque las metaclases en sí mismas también son objetos. Todo es un objeto, ya sea que se use para crear otros objetos o no, por lo que no se enrede en la semántica del uso de la clase de palabra en otros idiomas. En Python, una clase es realmente solo un objeto que se utiliza para determinar cómo crear otros objetos y cuáles serán sus comportamientos. Las metaclases son clases que crean clases, solo para ilustrar este punto.

Para proteger su variable compartida por otra instancia, debe crear una nueva variable de instancia cada vez que cree una instancia. Cuando declara una variable dentro de una clase, es una variable de clase y está compartida por todas las instancias. Si desea hacerlo por ejemplo, debe usar el método init para reinicializar la variable como referencia a la instancia

Desde Python Objects and Class de Programiz.com :

__init__() . Esta función especial se llama cada vez que se crea una instancia de un nuevo objeto de esa clase.

Este tipo de función también se denomina constructores en Progtwigción Orientada a Objetos (OOP). Normalmente lo usamos para inicializar todas las variables.

Por ejemplo:

 class example: list=[] #This is class variable shared by all instance def __init__(self): self.list = [] #This is instance variable referred to specific instance 

Creo que las respuestas proporcionadas son engañosas. Una propiedad definida dentro de una clase se convierte en una propiedad de instancia cuando se crea una instancia del objeto, independientemente de cómo lo defina . Entonces se hacen copias de a.list , y x.list y y.list son copias diferentes. La razón por la que parecen ser iguales es que ambos son alias de la misma lista. Pero eso es una consecuencia de la forma en que funcionan las listas, no de la forma en que funcionan las clases. Si hiciera lo mismo con números en lugar de listas (o simplemente usando + = en lugar de agregar, lo que crearía una nueva lista), vería que cambiar x.attr no afecta a cambiar y.attr .

La definición de self.list dentro de __init__ funciona, porque la función se llama dos veces, una vez por cada vez que se crea una instancia del objeto, y así, se crean dos listas diferentes.