¿Debo usar una clase o diccionario?

Tengo una clase que solo contiene campos y ningún método, como este:

class Request(object): def __init__(self, environ): self.environ = environ self.request_method = environ.get('REQUEST_METHOD', None) self.url_scheme = environ.get('wsgi.url_scheme', None) self.request_uri = wsgiref.util.request_uri(environ) self.path = environ.get('PATH_INFO', None) # ... 

Esto podría traducirse fácilmente a un dict. La clase es más flexible para futuras adiciones y podría ser rápida con __slots__ . Entonces, ¿habría un beneficio de usar un dict en su lugar? ¿Sería un dictado más rápido que una clase? ¿Y más rápido que una clase con tragamonedas?

¿Por qué demonios harías esto un diccionario? ¿Cuál es la ventaja? ¿Qué pasa si luego quieres agregar algún código? ¿A dónde iría tu código __init__ ?

Las clases son para agrupar datos relacionados (y generalmente código).

Los diccionarios son para almacenar relaciones clave-valor, donde generalmente las claves son todas del mismo tipo, y todos los valores son también de un tipo. Ocasionalmente, pueden ser útiles para agrupar datos cuando no se conocen todos los nombres de clave / atributo por adelantado, pero a menudo esto es una señal de que algo está mal con su diseño.

Mantenga esto una clase.

Use un diccionario a menos que necesite el mecanismo extra de una clase. También puede usar un namedtuple para un enfoque híbrido:

 >>> from collections import namedtuple >>> request = namedtuple("Request", "environ request_method url_scheme") >>> request  >>> request.environ = "foo" >>> request.environ 'foo' 

Las diferencias de rendimiento aquí serán mínimas, aunque me sorprendería si el diccionario no fuera más rápido.

Una clase en python es un dict debajo. Obtiene algunos gastos generales con el comportamiento de clase, pero no podrá notarlo sin un generador de perfiles. En este caso, creo que te beneficias de la clase porque:

  • Toda tu lógica vive en una sola función.
  • Es fácil de actualizar y permanece encapsulado.
  • Si cambia algo más tarde, puede mantener fácilmente la misma interfaz.

Creo que el uso de cada uno es demasiado subjetivo para que yo pueda entrar en eso, así que me limitaré a los números.

Comparé el tiempo que lleva crear y cambiar una variable en un dict, una clase new_style y una clase new_style con ranuras.

Aquí está el código que utilicé para probarlo (es un poco desordenado, pero cumple su función).

 import timeit class Foo(object): def __init__(self): self.foo1 = 'test' self.foo2 = 'test' self.foo3 = 'test' def create_dict(): foo_dict = {} foo_dict['foo1'] = 'test' foo_dict['foo2'] = 'test' foo_dict['foo3'] = 'test' return foo_dict class Bar(object): __slots__ = ['foo1', 'foo2', 'foo3'] def __init__(self): self.foo1 = 'test' self.foo2 = 'test' self.foo3 = 'test' tmit = timeit.timeit print 'Creating...\n' print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict')) print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo')) print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar')) print '\nChanging a variable...\n' print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] = "Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict'))) print 'Class: ' + str((tmit('Foo().foo3 = "Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo'))) print 'Class with slots: ' + str((tmit('Bar().foo3 = "Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar'))) 

Y aquí está la salida …

Creando …

 Dict: 0.817466186345 Class: 1.60829183597 Class_with_slots: 1.28776730003 

Cambiando una variable …

 Dict: 0.0735140918748 Class: 0.111714198313 Class_with_slots: 0.10618612142 

Entonces, si solo estás almacenando variables, necesitas velocidad, y no requerirá que hagas muchos cálculos, te recomiendo usar un dict (siempre puedes hacer una función que parezca un método). Pero, si realmente necesita clases, recuerde: siempre use __ slots __ .

Nota:

Probé la ‘Clase’ con las clases new_style y old_style. Resulta que las clases de estilo antiguo son más rápidas de crear pero más lentas de modificar (no por mucho pero sí significativas si estás creando muchas clases en un bucle cerrado (consejo: lo estás haciendo mal)).

Además, los tiempos para crear y cambiar variables pueden diferir en su computadora, ya que la mía es antigua y lenta. Asegúrese de probarlo usted mismo para ver los resultados “reales”.

Editar:

Más tarde probé el timbre nombrado: no puedo modificarlo, pero para crear las 10000 muestras (o algo así) tomó 1.4 segundos, por lo que el diccionario es el más rápido.

Si cambio la función de dictado para incluir las claves y los valores y para devolver el dictado en lugar de la variable que contiene el dictado cuando lo creo, me da 0,65 en lugar de 0,8 segundos.

 class Foo(dict): pass 

Crear es como una clase con ranuras y cambiar la variable es la más lenta (0.17 segundos), así que no use estas clases . ir para un dict (velocidad) o para la clase derivada de objeto (‘syntax dulce’)

Estoy de acuerdo con @adw. Nunca representaría un “objeto” (en un sentido OO) con un diccionario. Diccionarios de pares nombre / valor agregado. Las clases representan objetos. He visto un código donde los objetos están representados con diccionarios y no está claro cuál es la forma real de la cosa. ¿Qué sucede cuando no existen ciertos nombres / valores? Lo que restringe al cliente de poner algo en absoluto. O tratar de sacar algo del todo. La forma de la cosa siempre debe estar claramente definida.

Al usar Python, es importante construir con disciplina, ya que el lenguaje permite que el autor se dispare en el pie de muchas maneras.

Recomendaría una clase, ya que es todo tipo de información involucrada con una solicitud. Si uno usara un diccionario, esperaría que los datos almacenados fueran mucho más similares en naturaleza. Una pauta que tiendo a seguir a mí mismo es que si quiero recorrer todo el conjunto de pares clave-> valor y hacer algo, uso un diccionario. De lo contrario, los datos aparentemente tienen mucha más estructura que una clave básica> asignación de valores, lo que significa que una clase probablemente sería una mejor alternativa.

Por lo tanto, quédate con la clase.

Si todo lo que quieres lograr es syntax dulce como obj.bla = 5 lugar de obj['bla'] = 5 , especialmente si tienes que repetir eso mucho, tal vez quieras usar alguna clase de contenedor simple como en la sugerencia de martineaus . Sin embargo, el código allí es bastante abultado e innecesariamente lento. Puedes mantenerlo así de simple:

 class AttrDict(dict): """ Syntax candy """ __getattr__ = dict.__getitem__ __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__ 

Otra razón para cambiar a namedtuple s o una clase con __slots__ podría ser el uso de memoria. Los dictados requieren mucha más memoria que los tipos de lista, por lo que este podría ser un punto para pensar.

De todos modos, en su caso específico, no parece haber ninguna motivación para cambiar su implementación actual. Parece que no mantiene millones de estos objetos, por lo que no se requieren tipos derivados de listas. Y en realidad contiene alguna lógica funcional dentro del __init__ , por lo que tampoco debería tener AttrDict .

Puede ser posible tener su pastel y comerlo, también. En otras palabras, puede crear algo que proporcione la funcionalidad de una clase y una instancia de diccionario. Vea el Diccionario de ActiveState con receta de acceso de estilo de atributo y comentarios sobre cómo hacerlo.

Si decides utilizar una clase regular en lugar de una subclase, me parece que la receta de la clase ” simple pero práctica” para un coleccionista de un montón de cosas con nombre ” es muy flexible y útil para el tipo de cosas que te parecen” re haciendo (es decir, crear un agregador de información relativamente simple). Ya que es una clase, puede ampliar fácilmente su funcionalidad más adelante agregando métodos.

Por último, debe tenerse en cuenta que los nombres de los miembros de la clase deben ser identificadores legales de Python, pero las claves del diccionario no lo hacen, por lo que un diccionario proporcionaría mayor libertad en ese sentido, ya que las claves pueden ser cualquier cosa que se pueda marcar (incluso algo que no sea una cadena).

 class ClassWithSlotBase: __slots__ = ('a', 'b',) def __init__(self): self.a: str = "test" self.b: float = 0.0 def test_type_hint(_b: float) -> None: print(_b) class_tmp = ClassWithSlotBase() test_type_hint(class_tmp.a) 

Recomiendo una clase. Si usa una clase, puede obtener una sugerencia de tipo como se muestra. Y el soporte de clase se completa automáticamente cuando la clase es un argumento de función.

introduzca la descripción de la imagen aquí