Supongamos que tengo la siguiente clase:

class A:
    arr = []

Si agrego a arr para una instancia de A, todas las instancias de A se actualizan.

>>> a1, a2 = A(), A()
>>> a1.arr.append(0)
>>> a1.arr
[0]
>>> a2.arr
[0]
>>> A.arr
[0]

Sin embargo, si configuro arr en un literal de matriz para una instancia de A, otras instancias se no actualizan:

>>> a1.arr = [1,2,3]
>>> a1.arr
[1, 2, 3]
>>> a2.arr
[0]
>>> A.arr
[0]

¿Por qué ocurre esto? Cuando el atributo de clase es una lista, ¿por qué hay resultados diferentes entre append y =?

También noté un comportamiento similar cuando el atributo de clase no es una matriz:

class B:
    value = ''
>>> b1, b2 = B(), B()
>>> b1.value = 'hello'
>>> b1.value
'hello'
>>> b2.value
''
>>> B.value
''
>>> B.value = 'goodbye'
>>> b1.value
'hello'
>>> b2.value
'goodbye'
>>> B.value
'goodbye'

¿Por qué el comportamiento parece diferente cuando el atributo de clase es una cadena? Cuando el valor de b1 ya está configurado, ¿por qué B.value = ... solo actualiza el valor de b2 y no el de b1?

1
wcarhart 10 oct. 2019 a las 00:14

6 respuestas

La mejor respuesta

Estás confundido sobre el manejo de los atributos de clase y los atributos de instancia. Un atributo de instancia predeterminado al atributo de clase. Sin embargo, cuando cambia específicamente una instancia, crea un atributo de instancia. Veamos tu secuencia con la clase B:

class B:
    value = ''
# You have a single attribute, `B.value`

b1, b2 = B(), B()
b1.value = 'hello'
# This shadows b1's reference to B.value,
# inserting a local reference to its own attribute of the same name.
# You can check this with the id() function

b2.value   # this still refers to the class attribute.

¿Está claro desde aquí?

1
Prune 9 oct. 2019 a las 21:26

Creo que esta respuesta explica lo que está sucediendo.

En la clase A, arr es un atributo de clase :

... todas las instancias de Foo [A] comparten foobar [art]

Cuando .append(), estás operando directamente en el objeto de lista arr. Cuando asigna (a1.arr = [1, 2, 3]), crea un nuevo objeto de lista y lo asigna como un atributo de instancia (efectivamente self.arr) en a1 que tiene prioridad sobre el atributo de clase {{X5 }}.

Si no tocamos foovar, es lo mismo tanto para f como para Foo. Pero si cambiamos f.foovar ... << fragmento de código >> ... agregamos un atributo de instancia que enmascara efectivamente el valor de Foo.foovar. Ahora, si cambiamos Foo.foovar directamente, no afecta nuestra instancia de foo:

1
b_c 9 oct. 2019 a las 21:25

Cuando define un class variable, y le asigna una lista, la dirección de la lista se asignará al class variable:

class A:
    arr = []

Es por eso que en el primer caso, cuando agrega 0 a arr, se agregaría a todos los objetos arr.

Cuando asigna a1.arr = [1,2,3], la dirección de arr en el objeto a1 cambia, ¡esa es la razón por la que a2.arr no cambia!

Y sobre el segundo caso, está asignando el valor de una variable de cadena a value. así que si cambia b1.value, no cambia b2.value

class B:
    value = ''

Por cierto, en otros idiomas, este problema es exactamente sobre la diferencia entre reference y value.

1
Mohsen_Fatemi 9 oct. 2019 a las 21:31

Si estás preguntando cómo evitar esto?

class A:
    def __init__(self):
        self.ls = []
a,b = A(), A()
a.ls.append(0)

Usando

__init __ () hará que las instancias sean individuales

Aquí hay un ejemplo de lo que estás haciendo ...

class B:
ls = []
def __init__(self):
    pass


c,d = B(),B()
c.ls == d.ls
Out[21]: True

Como puede ver, todavía hacen referencia a la misma variable del objeto B.

Esto es porque

Uno es un atributo de clase, mientras que el otro es un atributo de instancia.

Entonces, la lista ls que se declara fuera de la instanciación es shared by all the instances of B.

-1
Yatish Kadam 10 oct. 2019 a las 16:41
class C:
    class_attribute=2

    def __init__(self):
        self.instance_attribute='boo'

Si consulta un atributo de una instancia de clase (my_instance.foo), el valor devuelto es un atributo de instancia si existe, si no es un atributo de clase

Si asigna a una instancia (my_instance.foo = 42), se crea un atributo de instancia, puede tener el mismo nombre y sombrea el atributo de clase

0
Derte Trdelnik 9 oct. 2019 a las 21:25

Nombre y enlace: ¿Qué hace "="

En pocas palabras, Python no tiene una variable real . La variable que ve es en realidad un nombre (como el alias en otros idiomas). Y el operador =, que siempre se llama Asignación , enlazar el nombre de un objeto . (En Python, todo es un objeto)

Por ejemplo:

x = 3

El = realmente no cambia el valor de x, porque en realidad no hay ninguna variable x que contenga un valor.
En su lugar, crea un objeto inmutable 3 y hace que x sea un nombre enlace (similar a la referencia de C ++ )

Entonces, si la hacemos

>>> a = [1,2]
>>> b = a
>>> print(id(a)) # id(object) will return the address of object in memory
2426261961288
>>> print(id(b))
2426261961288
>>> a is b # operator "is" evaluate whether a and b refer to the same object.  
True
>>> b.append(3)
>>> print(id(b)) # b's address didn't change
2426261961288
>>> print(a)
[1, 2, 3]
>>> print(b)
[1, 2, 3]

Primero, a = [1,2] une el nombre a a un objeto mutable , que es una lista [1, 2]. (Para una mejor comprensión, anotaría este apodo como un apodo OBJ_288 )
Luego, b = a une b al mismo objeto al que a hace referencia, OBJ_288 .
Puede ver que id(a) es lo mismo que id(b), lo que significa que sus direcciones son las mismas.
b.append(3) realmente cambia el objeto al que b está vinculado (ya que b.append se refiere a un método de OBJ_288 ). Ahora OBJ_288 se convierte en [1, 2, 3], a quien a y b están obligados.
Entonces, cuando print(a) y print(b), los resultados son los mismos.

Sin embargo, si la hacemos

>>> b = [4, 5, 6]
>>> a is b
False
>>> id(a)
2426261961288
>>> id(b)
2426262048840
>>> print(a)
[1, 2, 3]

Cuando llamamos al operador = para b, b se unirá a otro objeto (aquí está el nuevo objeto que creamos [4, 5, 6], apodémoslo OBJ840 )
Si bien a todavía se refiere a OBJ_288 , print(a) sigue siendo [1, 2, 3]

Para obtener detalles, consulte las siguientes referencias (si tiene conocimiento de C ++, podría entender las 2 primeras referencias más fácilmente):
https://realpython.com/pointers-in-python/#names- en python
https://eev.ee/blog/2012/05/23 / python-faq-pasando /
Además, las reglas detalladas se establecen en la Referencia oficial de Python.
https://docs.python.org/3/reference/ executemodel.html # naming-and-binding

Alcance, atributo de clase y atributo de instancia

En el código:

class A:
    arr = []
>>> a1, a2 = A(), A()
>>> a1.arr.append(0)

a1 es una instancia de class A, y en Python, para la resolución de nombre de atributo, cada instancia crea un espacio de nombres, que es el primer lugar en el que se encuentran las referencias de atributos buscado, si no se encuentra, continuará buscando en su espacio de nombres de clase (donde están los atributos de clase ) .
Entonces, en su caso, dado que a1 no tiene un atributo de instancia llamado arr, se refiere al atributo de clase A A.arr. y append es el método de A.arr, que modificaría A.arr, lo que da como resultado:

>>> A.arr
[0]

Pero si lo haces

>>> a1.arr = [1,2,3]

Recuerde que dije en Nombre y enlace: ¿Qué hace "=" , la asignación = uniría su nombre del lado izquierdo a su objeto del lado derecho.
Además, en Python, las asignaciones de nombres siempre van al ámbito más interno (excepto lo especificado por global o nonlocal) . Aquí significa que vinculará objeto [1,2,3] a a1.arr, que es el atributo de instancia de a1, incluso antes no existía. < br /> Ahora a1 tiene un nuevo atributo de instancia arr, por lo que a1.arr, como regla de resolución de nombre de atributo, sombreará A.arr. Es por eso:

>>> a1.arr
[1, 2, 3]

Y el atributo de clase de clase A A.arr no se ve afectado.

>>> a2.arr
[0]
>>> A.arr
[0]

Referencia:
https: //docs.pythonorg/3/reference/datamodel. html # the-standard-type-jerarquía elemento Instancias de clase
https: //docs.python. org / 3 / tutorial / classes.html # a-word-about-names-and-objects

0
Jokerkeny 12 oct. 2019 a las 04:49
58312396