Variables de instancia frente a variables de clase en Python

121

Tengo clases de Python, de las cuales solo necesito una instancia en tiempo de ejecución, por lo que sería suficiente tener los atributos solo una vez por clase y no por instancia. Si hubiera más de una instancia (lo que no sucederá), todas las instancias deberían tener la misma configuración. Me pregunto cuál de las siguientes opciones sería mejor o más "idiomática" en Python.

Variables de clase:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

Variables de instancia:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass
demonio
fuente
4
Después de leer esta pregunta y ver la respuesta, una de mis primeras preguntas fue: "Entonces, ¿cómo accedo a las variables de clase?" - eso es porque hasta este punto solo he usado variables de instancia. En respuesta a mi propia pregunta, lo hace a través del nombre de la clase, aunque técnicamente también puede hacerlo a través de una instancia. Aquí hay un enlace para leer para cualquier otra persona con la misma pregunta: stackoverflow.com/a/3434596/4561887
Gabriel Staples

Respuestas:

159

Si solo tiene una instancia de todos modos, es mejor hacer todas las variables por instancia, simplemente porque se accederá a ellas (un poco) más rápido (un nivel menos de "búsqueda" debido a la "herencia" de clase a instancia), y no hay inconvenientes para sopesar esta pequeña ventaja.

Alex Martelli
fuente
7
¿Nunca has oído hablar del patrón Borg? Tener solo una instancia era la forma incorrecta de tenerla en primer lugar.
Devin Jeanpierre
435
@Devin, sí, he oído hablar del patrón Borg, ya que fui yo quien lo introdujo (en 2001, cfr code.activestate.com/recipes/… ;-). Pero no hay nada de malo, en casos simples, en simplemente tener una sola instancia sin cumplimiento.
Alex Martelli
2
@ user1767754, es fácil hacerlos usted mismo python -mtimeit, pero después de hacerlo en python3.4, noto que acceder a una intvariable de clase es en realidad de 5 a 11 nanosegundos más rápido que la misma variable de instancia en mi antigua estación de trabajo, no estoy seguro de qué codepath lo hace así.
Alex Martelli
45

Repitiendo aún más los consejos de Mike y Alex y agregando mi propio color ...

El uso de atributos de instancia es el típico ... el Python más idiomático. Los atributos de clase no se utilizan tanto, ya que sus casos de uso son específicos. Lo mismo ocurre con los métodos estáticos y de clase frente a los métodos "normales". Son construcciones especiales que abordan casos de uso específicos; de lo contrario, es un código creado por un programador aberrante que quiere presumir que conocen algún rincón oscuro de la programación de Python.

Alex menciona en su respuesta que el acceso será (un poco) más rápido debido a un nivel menos de búsqueda ... permítanme aclarar más para aquellos que aún no saben cómo funciona esto. Es muy similar al acceso variable, cuyo orden de búsqueda es:

  1. lugareños
  2. no locales
  3. globales
  4. incorporados

Para el acceso a atributos, el orden es:

  1. ejemplo
  2. clase
  3. clases base determinadas por el MRO (orden de resolución del método)

Ambas técnicas funcionan de manera "de adentro hacia afuera", lo que significa que los objetos más locales se verifican primero, luego las capas externas se verifican sucesivamente.

En su ejemplo anterior, digamos que está buscando el pathatributo. Cuando encuentra una referencia como " self.path", Python buscará primero en los atributos de la instancia una coincidencia. Cuando eso falla, verifica la clase desde la que se creó la instancia del objeto. Finalmente, buscará las clases base. Como dijo Alex, si su atributo se encuentra en la instancia, no necesita buscar en otro lado, de ahí su pequeño ahorro de tiempo.

Sin embargo, si insiste en los atributos de clase, necesita esa búsqueda adicional. O su otra alternativa es hacer referencia al objeto a través de la clase en lugar de la instancia, por ejemplo, en MyController.pathlugar de self.path. Esa es una búsqueda directa que evitará la búsqueda diferida, pero como Alex menciona a continuación, es una variable global, por lo que pierde ese bit que pensó que iba a guardar (a menos que cree una referencia local al nombre de clase [global] ).

La conclusión es que debe usar atributos de instancia la mayor parte del tiempo. Sin embargo, habrá ocasiones en las que un atributo de clase sea la herramienta adecuada para el trabajo. El uso de código que use ambos al mismo tiempo requerirá la mayor diligencia, porque el uso selfsolo le dará el objeto de atributo de instancia y el acceso de sombras al atributo de clase del mismo nombre. En este caso, debe utilizar el acceso al atributo por el nombre de la clase para hacer referencia a él.

wescpy
fuente
@wescpy, pero MyControllerse busca en los globales, por lo que el costo total es más alto que self.pathdonde pathes una variable de instancia (ya que selfes local al método == búsqueda súper rápida).
Alex Martelli
ah, cierto. buena atrapada. Supongo que la única solución es crear una referencia local ... en este punto, realmente no vale la pena.
wescpy
24

En caso de duda, probablemente desee un atributo de instancia.

Los atributos de clase se reservan mejor para casos especiales en los que tienen sentido. El único caso de uso muy común son los métodos. No es raro usar atributos de clase para constantes de solo lectura que las instancias necesitan saber (aunque el único beneficio de esto es si también desea acceso desde fuera de la clase), pero ciertamente debe tener cuidado al almacenar cualquier estado en ellas. , que rara vez es lo que quieres. Incluso si solo tendrá una instancia, debe escribir la clase como lo haría con cualquier otra, lo que generalmente significa usar atributos de instancia.

Mike Graham
fuente
1
Las variables de clase son una especie de constantes de solo lectura. Si Python me dejara definir constantes, lo habría escrito como constantes.
Deamon
1
@deamon, es un poco más probable que ponga mis constantes completamente fuera de las definiciones de una clase y las nombre en mayúsculas. Ponerlos dentro de la clase también está bien. Hacerlos atributos de instancia no dañará nada, pero puede ser un poco extraño. No creo que este sea un problema en el que la comunidad apoye demasiado una de las opciones.
Mike Graham
@MikeGraham FWIW, la Guía de estilo Python de Google sugiere evitar las variables globales en favor de las variables de clase. Sin embargo, existen excepciones.
Dennis
Aquí hay un nuevo enlace a la Guía de estilo Python de Google . Ahora simplemente está escrito: avoid global variablesy su definición es que las variables globales también son variables que se declaran como atributos de clase. Sin embargo, la propia guía de estilo de Python ( PEP-8 ) debería ser el primer lugar al que acudir para preguntas de este tipo. Entonces su propia mente debería ser la herramienta elegida (por supuesto, también puede obtener ideas de Google, por ejemplo).
colidyre
4

La misma pregunta en Rendimiento de acceder a variables de clase en Python : el código aquí adaptado de @Edward Loper

Las variables locales son las más rápidas de acceder, prácticamente vinculadas con las variables del módulo, seguidas de las variables de clase, seguidas de las variables de instancia.

Hay 4 ámbitos desde los que puede acceder a las variables:

  1. Variables de instancia (self.varname)
  2. Variables de clase (Classname.varname)
  3. Variables del módulo (VARNAME)
  4. Variables locales (varname)

La prueba:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

El resultado:

access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199
robertcollier4
fuente