¿Nueva instancia de clase con un atributo de clase no-Ninguno?

Tengo una clase de Python que tiene un atributo de clase establecido en algo distinto de None . Al crear una nueva instancia, los cambios realizados en ese atributo se perpetúan en todas las instancias.

Aquí hay un código para dar sentido a esto:

  class Foo(object): a = [] b = 2 foo = Foo() foo.a.append('item') foo.b = 5 

El uso de foo.a devuelve ['item'] y foo.b devuelve 5 , como cabría esperar.

Cuando creo una nueva instancia (la llamaremos bar ), usando bar.a devuelve ['item'] y bar.b return 5 , también! Sin embargo, cuando inicialmente establecí todos los atributos de la clase en None , los __init__ en __init__ , así:

  class Foo(object): a = None b = None def __init__(self): self.a = [] self.b = 2 

El uso de bar.a devuelve [] y bar.b devuelve 2 mientras que foo.a devuelve ['item'] y foo.b devuelve 5 .

¿Es así como se supone que funciona? Al parecer, nunca me he topado con este problema en los 3 años que he progtwigdo Python y me gustaría una aclaración. Tampoco puedo encontrarlo en ninguna parte de la documentación, por lo que me sería de gran ayuda si me fuera posible obtener una referencia. 🙂

Sí, así es como se supone que funciona.

Si a y b pertenecen a la instancia de Foo , entonces la forma correcta de hacerlo es:

 class Foo(object): def __init__(self): self.a = [] self.b = 2 

Lo siguiente hace que a y b pertenezcan a la clase en sí , por lo que todas las instancias comparten las mismas variables:

 class Foo(object): a = [] b = 2 

Cuando mezcla los dos métodos, como lo hizo en su segundo ejemplo, esto no agrega nada útil, y solo causa confusión.

Una advertencia que vale la pena mencionar es que cuando haga lo siguiente en su primer ejemplo:

 foo.b = 5 

no está cambiando Foo.b , está agregando un atributo nuevo a foo que “sombrea” Foo.b Cuando haces esto, ni bar.b ni Foo.b cambian. Si posteriormente hace del foo.b , eso eliminará ese atributo y foo.b volverá a referirse a Foo.b

Sí, eso es exactamente lo que debe esperar. Cuando define una variable en la clase, esos atributos se adjuntan a la clase, no a la instancia. Es posible que no siempre note que cuando asigna un atributo en una instancia, la instancia recoge el nuevo valor, ocultando el atributo de clase. Los métodos que se modifican en su lugar , como list.append , no le darán a la instancia un nuevo atributo, ya que solo modifican el objeto existente, que es un atributo de la clase.

Cada vez que cada instancia de una clase debe tener su propio valor único para un atributo, generalmente debe establecerlo en el método __init__ , para asegurarse de que sea diferente para cada instancia.

solo cuando una clase tiene un atributo que tiene un valor predeterminado razonable, y ese valor es de un tipo inmutable (que no se puede modificar en su lugar), como int o str , debe establecer atributos en la clase.

Sí, parece sorprendente al principio, pero es la forma en que funciona python, como se dijo en otras respuestas. ¿Tres años de python sin ser reducido por esto? Tienes suerte, mucha suerte. Si yo fuera tú, revisaría el código sensible del pasado para ver si hay animales pequeños …

También tenga cuidado con las llamadas de función: def func(list=[]) tiene un comportamiento similar, que puede ser sorprendente.

Y, si es posible, conecte un análisis de pylint en sus ganchos de confirmación. O, al menos, ejecute pylint en su código, es muy instructivo, y probablemente le habría hablado de este truco (lo cual no es un truco, de hecho, es muy lógico y ortogonal, solo a la manera de los pitones).

Estás confundiendo un atributo de clase con un atributo de instancia. En su primer ejemplo, solo hay los atributos de clase a y b , pero en el segundo también tiene atributos de instancia con los mismos nombres.

Parece que Python pasará a través de las referencias a un atributo de una instancia a una clase si no hay un atributo de instancia (¡hoy aprendí algo nuevo!).

Puede encontrar este código instructivo:

 class Foo: a = 1 b = 2 def __init__(self): self.a=3 print Foo.a # => 1 print Foo().a # => 3 print Foo.b # => 2 print Foo().b # => 2