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?
- 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.
-
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
- Carece de mecanismos para manejar argumentos destacados
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.
Por cierto. Raymond Hettinger (desarrollador principal de Python) tuvo una gran charla en PyCon 2018:
https://www.youtube.com/watch?v=T-TwcmT6Rcw&t=1390
Las diapositivas están aquí: https://twitter.com/raymondh/status/995693882812915712