dataclasses data classes python class python-3.7 python-dataclasses

python - data - ¿Qué son las clases de datos y en qué se diferencian de las clases comunes?



python 3.7 dataclasses (5)

Con PEP 557, las clases de datos se introducen en la biblioteca estándar de Python.

Utilizan el decorador @dataclass y se supone que son "tuplas con nombre mutables con valor predeterminado", pero no estoy seguro de entender lo que esto realmente significa y en qué se diferencian de las clases comunes.

¿Qué son exactamente las clases de datos de Python y cuándo es mejor usarlas?


Visión general

La pregunta ha sido abordada. Sin embargo, esta respuesta agrega algunos ejemplos prácticos para ayudar en la comprensión básica de las clases de datos.

¿Qué son exactamente las clases de datos de Python y cuándo es mejor usarlas?

  1. generadores de código : generar código repetitivo; puede optar por implementar métodos especiales en una clase regular o hacer que una clase de datos los implemente automáticamente.
  2. Contenedores de datos : estructuras que contienen datos (por ejemplo, tuplas y dictados), a menudo con acceso punteado y de atributos, como clases, nombre de namedtuple y otros .

"nombre de tuplas mutables con [s] predeterminado"

Esto es lo que significa la última frase:

  • mutable : por defecto, los atributos de la clase de datos se pueden reasignar. Opcionalmente, puede hacerlos inmutables (consulte los ejemplos a continuación).
  • namedtuple : tienes acceso de puntos y atributos como un namedtuple o una clase regular.
  • predeterminado : puede asignar valores predeterminados a los atributos

En comparación con las clases comunes, principalmente ahorras escribiendo código repetitivo.

Caracteristicas

Aquí hay una descripción general de las características de la clase de datos (ver ejemplos en la Tabla de resumen).

Lo que obtienes

Estas son las características que obtiene por defecto de las clases de datos.

Atributos + Representación + Comparación

import dataclasses @dataclasses.dataclass #@dataclasses.dataclass() # alternative class Color: r : int = 0 g : int = 0 b : int = 0

Los siguientes valores predeterminados se configuran automáticamente en True :

@dataclasses.dataclass(init=True, repr=True, eq=True)

Lo que puedes encender

Las funciones adicionales están disponibles si las palabras clave apropiadas se establecen en True .

Orden

@dataclasses.dataclass(order=True) class Color: r : int = 0 g : int = 0 b : int = 0

Ahora se implementan los métodos de pedido (operadores de sobrecarga: < > <= >= functools.total_ordering < > <= >= ), de manera similar a functools.total_ordering con pruebas de igualdad más fuertes.

Hashable, Mutable

@dataclasses.dataclass(unsafe_hash=True) # override base `__hash__` class Color: ...

Aunque el objeto es potencialmente mutable (posiblemente no deseado), se implementa un hash.

Hashable, inmutable

@dataclasses.dataclass(frozen=True) # `eq=True` (default) to be immutable class Color: ...

Ahora se implementa un hash y no se permite cambiar el objeto o asignar atributos.

En general, el objeto es hashable si unsafe_hash=True o frozen=True .

Consulte también la tabla lógica de hashing original con más detalles.

Lo que no entiendes

Para obtener las siguientes características, se deben implementar métodos especiales manualmente:

Desempacables

@dataclasses.dataclass class Color: r : int = 0 g : int = 0 b : int = 0 def __iter__(self): yield from dataclasses.astuple(self)

Mejoramiento

@dataclasses.dataclass class SlottedColor: __slots__ = ["r", "b", "g"] r : int g : int b : int

El tamaño del objeto ahora se reduce:

>>> imp sys >>> sys.getsizeof(Color) 1056 >>> sys.getsizeof(SlottedColor) 888

En algunas circunstancias, __slots__ también mejora la velocidad de creación de instancias y acceso a atributos. Además, los espacios no permiten asignaciones predeterminadas; de lo contrario, se ValueError un ValueError .

Vea más sobre tragamonedas en esta publicación de blog .

Tabla de resumen

+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+ | Feature | Keyword | Example | Implement in a Class | +----------------------+----------------------+----------------------------------------------------+-----------------------------------------+ | Attributes | init | Color().r -> 0 | __init__ | | Representation | repr | Color() -> Color(r=0, g=0, b=0) | __repr__ | | Comparision* | eq | Color() == Color(0, 0, 0) -> True | __eq__ | | | | | | | Order | order | sorted([Color(0, 50, 0), Color()]) -> ... | __lt__, __le__, __gt__, __ge__ | | Hashable | unsafe_hash/frozen | {Color(), {Color()}} -> {Color(r=0, g=0, b=0)} | __hash__ | | Immutable | frozen + eq | Color().r = 10 -> TypeError | __setattr__, __delattr__ | | | | | | | Unpackable+ | - | r, g, b = Color() | __iter__ | | Optimization+ | - | sys.getsizeof(SlottedColor) -> 888 | __slots__ | +----------------------+----------------------+----------------------------------------------------+-----------------------------------------+

+ Estos métodos no se generan automáticamente y requieren implementación manual en una clase de datos.

* __ne__ no está implementado .

Características adicionales

Post-inicialización

@dataclasses.dataclass class RGBA: r : int = 0 g : int = 0 b : int = 0 a : float = 1.0 def __post_init__(self): self.a : int = int(self.a * 255) RGBA(127, 0, 255, 0.5) # RGBA(r=127, g=0, b=255, a=127)

Herencia

@dataclasses.dataclass class RGBA(Color): a : int = 0

Conversiones

Convierta una clase de datos en una tupla o un dict, recursively :

>>> dataclasses.astuple(Color(128, 0, 255)) (128, 0, 255) >>> dataclasses.asdict(Color(128, 0, 255)) {r: 128, g: 0, b: 255}

Limitaciones

Referencias

  • talk R. Hettinger sobre Dataclasses: el generador de código para finalizar todos los generadores de código
  • talk T. Hunner sobre Clases más fáciles: Clases de Python sin todo lo rudo
  • documentation de Python sobre detalles de hashing
  • guide Real Python sobre La guía definitiva para las clases de datos en Python 3.7
  • La publicación del blog de A. Shaw en Un breve recorrido por las clases de datos de Python 3.7
  • El repositorio github de E. Smith en clases de datos

Considere esta simple clase Foo

from dataclasses import dataclass @dataclass class Foo: def bar(): pass

Aquí está la comparación incorporada dir() . En el lado izquierdo está el Foo sin el decorador @dataclass, y a la derecha está el decorador @dataclass.

Aquí hay otra diferencia, después de usar el módulo de inspect para la comparación.


De la especificación PEP :

Se proporciona un decorador de clase que inspecciona una definición de clase para variables con anotaciones de tipo como se define en PEP 526, "Sintaxis para anotaciones variables". En este documento, tales variables se llaman campos. Usando estos campos, el decorador agrega definiciones de métodos generados a la clase para admitir la inicialización de instancias, una repr, métodos de comparación y, opcionalmente, otros métodos como se describe en la sección Especificación. Dicha clase se llama Clase de datos, pero en realidad no tiene nada de especial: el decorador agrega métodos generados a la clase y devuelve la misma clase que se le dio.

El generador @dataclass agrega métodos a la clase que de otro modo te definirías como __repr__ , __init__ , __lt__ y __gt__ .


Las clases de datos son solo clases regulares que están orientadas al almacenamiento de estado, más que contienen mucha lógica. Cada vez que crea una clase que consiste principalmente en atributos, crea una clase de datos.

Lo que hace el módulo de clases de datos es facilitar la creación de clases de datos. Se encarga de una gran cantidad de placa de caldera para usted.

Esto es especialmente importante cuando su clase de datos debe ser hashaable; Esto requiere un método __hash__ así como un método __eq__ . Si agrega un método __repr__ personalizado para facilitar la depuración, eso puede volverse bastante detallado:

class InventoryItem: ''''''Class for keeping track of an item in inventory.'''''' name: str unit_price: float quantity_on_hand: int = 0 def __init__( self, name: str, unit_price: float, quantity_on_hand: int = 0 ) -> None: self.name = name self.unit_price = unit_price self.quantity_on_hand = quantity_on_hand def total_cost(self) -> float: return self.unit_price * self.quantity_on_hand def __repr__(self) -> str: return ( ''InventoryItem('' f''name={self.name!r}, unit_price={self.unit_price!r}, '' f''quantity_on_hand={self.quantity_on_hand!r})'' def __hash__(self) -> int: return hash((self.name, self.unit_price, self.quantity_on_hand)) def __eq__(self, other) -> bool: if not isinstance(other, InventoryItem): return NotImplemented return ( (self.name, self.unit_price, self.quantity_on_hand) == (other.name, other.unit_price, other.quantity_on_hand))

Con las dataclasses puede reducirlo a:

from dataclasses import dataclass @dataclass(unsafe_hash=True) class InventoryItem: ''''''Class for keeping track of an item in inventory.'''''' name: str unit_price: float quantity_on_hand: int = 0 def total_cost(self) -> float: return self.unit_price * self.quantity_on_hand

El mismo decorador de clase también puede generar métodos de comparación ( __lt__ , __gt__ , etc.) y manejar la inmutabilidad.

namedtuple clases namedtuple también son clases de datos, pero son inmutables por defecto (además de ser secuencias). dataclasses son mucho más flexibles a este respecto, y pueden estructurarse fácilmente de modo que puedan desempeñar el mismo papel que una clase con nombre de namedtuple .

El PEP se inspiró en el proyecto attrs , que puede hacer aún más (incluidos slots, validadores, convertidores, metadatos, etc.).

Si desea ver algunos ejemplos, recientemente utilicé dataclasses de dataclasses para varias de mis soluciones de Adviento de Código , vea las soluciones para el día 7 , día 8 , día 11 y día 20 .

Si desea usar el módulo de dataclasses en las versiones de Python <3.7, puede instalar el módulo con respaldo (requiere 3.6) o usar el proyecto attrs mencionado anteriormente.