¿Cuál es la diferencia entre atributos de clase e instancia?
-
03-07-2019 - |
Pregunta
¿Existe alguna distinción significativa entre:
class A(object):
foo = 5 # some default value
vs.
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 lees el código, ¿consideras que el significado de los dos estilos es significativamente diferente?
Solución
Más allá de las consideraciones de rendimiento, existe una diferencia significativa semántica . En el caso del atributo de clase, solo se hace referencia a un objeto. En el conjunto de atributos de instancia en la instanciación, puede haber varios objetos a los que se hace referencia. 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
[]
Otros consejos
La diferencia es que el atributo en la clase es compartido por todas las instancias. El atributo en una instancia es exclusivo de esa instancia.
Si viene de C ++, los atributos en la clase son más como variables miembro estáticas.
Aquí hay una muy buena publicación , y resumirla como 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
Asignación de atributos de clase
-
Si un atributo de clase se establece accediendo a la clase, anulará el valor de 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 accediendo 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, intuitivamente, 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 utilizarías el atributo de clase?
Almacenamiento de constantes . Como se puede acceder a los atributos de clase como atributos de la clase en sí, & # 8217; a menudo es bueno usarlos para almacenar constantes específicas de 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
Definición de valores predeterminados . Como ejemplo trivial, podríamos crear una lista acotada (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 las personas 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 sobre Alex Coventry .
El hecho de que Alex asigne 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 pregunta por qué usé object()
en lugar de, por ejemplo, 5
, eso es para evitar encontrarse con otros dos problemas que no quiero abordar aquí; por dos diferentes razones, los a.foo.append(5)
s completamente creados por separado pueden terminar siendo la misma instancia del número b.foo
. Pero los a.foo = 5
s completamente creados por separado no pueden.
Entonces, ¿por qué a.foo
en el ejemplo de Alex afecta a shared_ptr<T>
, pero T
en mi ejemplo no? Bueno, intente <=> en el ejemplo de Alex y observe que no afecta a <=> allí tampoco .
<=> solo está convirtiendo <=> en un nombre para <=>. Eso no afecta a <=>, ni a ningún otro nombre para el valor anterior al que <=> solía referirse. * Es un poco complicado que estemos creando un atributo de instancia que oculte un atributo de clase, ** pero una vez que entiendo eso, aquí no pasa nada complicado.
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 provienen de un lenguaje como C ++ es que en Python, los valores no se almacenan en variables. Los valores viven en tierra de valor, por sí solos, las variables son solo nombres para valores, y la asignación solo crea un nuevo nombre para un valor. Si ayuda, piense en cada variable de Python como un <=> en lugar de un <=>.
** Algunas personas se aprovechan de esto usando un atributo de clase como " valor predeterminado " para un atributo de instancia que las instancias pueden o no establecerse. Esto puede ser útil en algunos casos, pero también puede ser confuso, así que tenga cuidado con eso.
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()
Lo anterior generará:
('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)
¡El mismo tipo de acceso a la instancia a través de clase o instancia devuelve resultados diferentes!
Y encontré en c.PyObject_GenericGetAttr definicióny un gran correo.
Explicar
Si el atributo se encuentra en el diccionario de las clases que lo componen.los objetos MRO, luego verifique si el atributo que se está buscando apunta a un Descriptor de datos (que no es más que una clase que implementa tanto el
__get__
y el__set__
métodos).Si es así, resuelva la búsqueda de atributos llamando al__get__
método del Descriptor de datos (líneas 28 a 33).