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?

¿Fue útil?

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

enter descripción de la imagen aquí

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).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top