¿Cuál es la diferencia entre los atributos de clase y instancia?

¿Hay alguna distinción significativa entre:

class A(object): foo = 5 # some default value 

contra

 class B(object): def __init__(self, foo=5): self.foo = foo 

Si está creando muchas instancias, ¿hay alguna diferencia en el rendimiento o los requisitos de espacio para los dos estilos? Cuando lee el código, ¿considera que el significado de los dos estilos es significativamente diferente?

Más allá de las consideraciones de rendimiento, hay una diferencia semántica significativa. En el caso del atributo de clase, solo se hace referencia a un objeto. En la instancia de atributo-conjunto-a-instanciación, puede haber múltiples objetos referidos. Por ejemplo

 >>> class A: foo = [] >>> a, b = A(), A() >>> a.foo.append(5) >>> b.foo [5] >>> class A: ... def __init__(self): self.foo = [] >>> a, b = A(), A() >>> a.foo.append(5) >>> b.foo [] 

La diferencia es que el atributo en la clase es compartido por todas las instancias. El atributo en una instancia es único para esa instancia.

Si provienen de C ++, los atributos en la clase son más bien como variables miembro estáticas.

Aquí hay una muy buena publicación , y resúmala como se muestra a continuación.

 class Bar(object): ## No need for dot syntax class_var = 1 def __init__(self, i_var): self.i_var = i_var ## Need dot syntax as we've left scope of class namespace Bar.class_var ## 1 foo = MyClass(2) ## Finds i_var in foo's instance namespace foo.i_var ## 2 ## Doesn't find class_var in instance namespace… ## So look's in class namespace (Bar.__dict__) foo.class_var ## 1 

Y en forma visual

introduzca la descripción de la imagen aquí

Asignación de atributo de clase

  • Si se establece un atributo de clase al acceder a la clase, se anulará el valor para todas las instancias

     foo = Bar(2) foo.class_var ## 1 Bar.class_var = 2 foo.class_var ## 2 
  • Si una variable de clase se establece al acceder a una instancia, anulará el valor solo para esa instancia . Esto esencialmente anula la variable de clase y la convierte en una variable de instancia disponible, de manera intuitiva, solo para esa instancia .

     foo = Bar(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 Bar.class_var ## 1 

¿Cuándo usarías el atributo de clase?

  • Almacenamiento de constantes . Como los atributos de la clase se pueden acceder como atributos de la clase en sí, a menudo es bueno usarlos para almacenar constantes específicas de la clase en toda la clase.

     class Circle(object): pi = 3.14159 def __init__(self, radius): self.radius = radius def area(self): return Circle.pi * self.radius * self.radius Circle.pi ## 3.14159 c = Circle(10) c.pi ## 3.14159 c.area() ## 314.159 
  • Definiendo valores por defecto . Como ejemplo trivial, podríamos crear una lista limitada (es decir, una lista que solo puede contener un cierto número de elementos o menos) y elegir tener un límite predeterminado de 10 elementos.

     class MyClass(object): limit = 10 def __init__(self): self.data = [] def item(self, i): return self.data[i] def add(self, e): if len(self.data) >= self.limit: raise Exception("Too many elements") self.data.append(e) MyClass.limit ## 10 

Dado que la gente en los comentarios aquí y en otras dos preguntas marcadas como “dups” parecen estar confundidas acerca de esto de la misma manera, creo que vale la pena agregar una respuesta adicional a la de Alex Coventry .

El hecho de que Alex esté asignando un valor de tipo mutable, como una lista, no tiene nada que ver con si las cosas se comparten o no. Podemos ver esto con la función id o el operador is :

 >>> class A: foo = object() >>> a, b = A(), A() >>> a.foo is b.foo True >>> class A: ... def __init__(self): self.foo = object() >>> a, b = A(), A() >>> a.foo is b.foo False 

(Si se está preguntando por qué usé object() lugar de, digamos, 5 , eso es para evitar tener que enfrentar otros dos problemas que no quiero abordar aquí; por dos razones diferentes, 5 s creados por separado) puede terminar siendo la misma instancia del número 5 Pero el object() creado completamente por separado no puede).


Entonces, ¿por qué es que a.foo.append(5) en el ejemplo de Alex afecta a b.foo , pero a.foo = 5 en mi ejemplo no? Bueno, prueba a.foo = 5 en el ejemplo de Alex, y observa que tampoco afecta a b.foo allí.

a.foo = 5 solo está convirtiendo a.foo en un nombre para 5 . Eso no afecta a b.foo , ni a ningún otro nombre para el antiguo valor que a.foo usaba para referirse. * Es un poco complicado que estemos creando un atributo de instancia que oculte un atributo de clase, ** pero una vez que entiendo eso, nada complicado está pasando aquí.


Esperemos que ahora sea obvio por qué Alex usó una lista: el hecho de que pueda mutar una lista significa que es más fácil mostrar que dos variables nombran la misma lista, y también significa que es más importante en el código de la vida real saber si tiene dos listas o Dos nombres para la misma lista.


* La confusión para las personas que vienen de un lenguaje como C ++ es que en Python, los valores no se almacenan en variables. Los valores viven fuera de la tierra de valores, por sí solos, las variables son solo nombres de valores, y la asignación simplemente crea un nuevo nombre para un valor. Si ayuda, piense en cada variable de Python como shared_ptr lugar de una T

** Algunas personas aprovechan esto al usar un atributo de clase como un “valor predeterminado” para un atributo de instancia que las instancias pueden o no establecer. Esto puede ser útil en algunos casos, pero también puede ser confuso, así que ten cuidado.

Solo una explicación de lo que dijo Alex Coventry, otro Alex (Martelli) abordó una pregunta similar en el grupo de noticias comp.lang.python años atrás. Examina la diferencia semántica de lo que una persona pretende en comparación con lo que obtuvo (mediante el uso de variables de instancia).

http://groups.google.com/group/comp.lang.python/msg/5914d297aff35fae?hl=es

Hay una situación más.

Los atributos de clase e instancia son Descriptor .

 # -*- encoding: utf-8 -*- class RevealAccess(object): def __init__(self, initval=None, name='var'): self.val = initval self.name = name def __get__(self, obj, objtype): return self.val class Base(object): attr_1 = RevealAccess(10, 'var "x"') def __init__(self): self.attr_2 = RevealAccess(10, 'var "x"') def main(): b = Base() print("Access to class attribute, return: ", Base.attr_1) print("Access to instance attribute, return: ", b.attr_2) if __name__ == '__main__': main() 

Por encima se producirá:

 ('Access to class attribute, return: ', 10) ('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>) 

¡El mismo tipo de acceso de instancia a través de clase o instancia devuelve un resultado diferente!

Y encontré en la definición de c.PyObject_GenericGetAttr , y una gran publicación .

Explique

Si el atributo se encuentra en el diccionario de las clases que componen. los objetos MRO, luego verifican si el atributo que se está buscando apunta a un Descriptor de Datos (que no es más que una clase que implementa los __get__ y __set__ ). Si lo hace, resuelva la búsqueda de atributos llamando al método __get__ del Descriptor de datos (líneas 28–33).