español - python classes functions
¿Cómo evitar compartir datos de clase entre instancias? (7)
Aunque la respuesta aceptada es acertada, me gustaría agregar una descripción de bit.
Hagamos un pequeño ejercicio
primero que todo, 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
Muy sencillo hasta ahora, ¿sí? Ahora comencemos a jugar con esta clase. Inicialicemos 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 demonios funcionó A.temp
? Bueno, 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 que tiene 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 modificado se reflejará en todas las instancias de la clase A. Avancemos y hagamos eso:
>>> A.temp = ''Monuments''
>>> print A.temp
Monuments
>>> print a.temp
Monuments
Interesante ¿no? Y tenga en cuenta que id (a.temp) y id (A.temp) siguen siendo los mismos
Cualquier objeto Python recibe 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'': <function change at 0x7f5e26fee6e0>,
''__module__'': ''__main__'',
''__init__'': <function __init__ at 0x7f5e26fee668>,
''temp'': ''Monuments'',
''__doc__'': None
}
>>> print a.__dict__
{x: ''Tesseract''}
Tenga en cuenta que el atributo de temp
se enumera entre los atributos de la clase A mientras que x se muestra para la instancia
Entonces, ¿cómo es que obtenemos un valor definido de a.temp
si ni siquiera está en la lista para la instancia a. Bueno, esa es la magia del __getattribute__()
. En Python, la sintaxis de puntos invoca automáticamente este método, de modo que cuando escribimos a.temp
, Python ejecuta un. getattribute (''temp''). Ese método realiza la acción de búsqueda de atributos, es decir, encuentra el valor del atributo mirando 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'']
De acuerdo, ahora usemos nuestro método de change
:
>>> a.change(''Intervals'')
>>> print a.temp
Intervals
>>> print A.temp
Monuments
Bueno, ahora que hemos usado uno mismo, 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
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?
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 . Por lo tanto, 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 esa es una consecuencia de la forma en que funcionan las listas, no de la forma en que funcionan las clases. Si hicieras lo mismo con números en lugar de listas (o simplemente usando + = en lugar de agregar, lo que crearía una nueva lista) x.attr
que cambiar x.attr
no afecta el cambio de y.attr
.
La definición de self.list
dentro de __init__
funciona, porque la función se llama dos veces, una por cada vez que se crea una instancia del objeto, y así, se crean dos listas diferentes.
Entonces, casi todas las respuestas aquí parecen perder un punto en particular. Las variables de clase nunca se convierten en variables de instancia como lo demuestra el siguiente código. Al utilizar una metaclase para interceptar la asignación de variable en el nivel de clase, podemos ver que cuando a.myattr se reasigna, no se llama al método de asignación de campo de magia 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 todavía permite la asignación de campo.
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
IN SHORT Las variables de clase no tienen NADA que ver con las variables de instancia.
Más claramente Solo están en el alcance para 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 también son objetos. Todo es un objeto, ya sea que se use para crear otros objetos o no, así que no te metas en la semántica del uso de otros idiomas de la clase de palabras. En Python, una clase es realmente solo un objeto que se usa 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.
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 al atributo de list
después de la instanciación; por lo tanto, sigue siendo un atributo de clase. La lista de definición dentro de __init__
funciona porque __init__
se __init__
después de la instanciación. Alternativamente, este código también produciría el resultado deseado:
>>> 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 se puede cambiar 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''
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 le asigna un valor a un atributo después de la instanciación.
Sí, debe declarar en el "constructor" si desea que la lista se convierta en una propiedad del objeto y no en una propiedad de la clase.
Tu quieres esto:
class a:
def __init__(self):
self.list = []
Declarar las variables dentro de la declaración de clase los convierte en miembros de "clase" y no miembros de instancia. Al declararlos dentro del método __init__
, se asegura que se crea una nueva instancia de los miembros junto con cada instancia nueva del objeto, que es el comportamiento que está buscando.
Usted declaró "lista" como una "propiedad de nivel de clase" y no como "propiedad de nivel de instancia". Para que las propiedades tengan un alcance en el nivel de instancia, debe inicializarlas mediante una referencia con el parámetro "self" en el método __init__
(o en otro lugar según la situación).
No es estrictamente necesario inicializar las propiedades de la instancia en el método __init__
pero facilita la comprensión.