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


141

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

Utilizan el @dataclassdecorador y se supone que son "tuplas con nombre mutables con valor predeterminado", pero no estoy realmente 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?


8
Dado el extenso contenido de la PEP, ¿qué más podría desear saber? namedtupleLos s son inmutables y no pueden tener valores predeterminados para los atributos, mientras que las clases de datos son mutables y pueden tenerlos.
jonrsharpe

31
@jonrsharpe Me parece razonable que haya un hilo de stackoverflow en el tema. Stackoverflow está destinado a ser una enciclopedia en formato de preguntas y respuestas, ¿no? La respuesta nunca es "solo mira en este otro sitio web". No debería haber habido votos negativos aquí.
Luke Davis el

12
Hay cinco hilos sobre cómo agregar un elemento a una lista. Una pregunta sobre @dataclassno hará que el sitio se desintegre.
Eric

2
@jonrsharpe namedtuplesPUEDE tener valores predeterminados. Eche un vistazo aquí: stackoverflow.com/questions/11351032/…
MJB

Respuestas:


152

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

Lo que hace el dataclassesmódulo 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 __hash__método y un __eq__método. Si agrega un __repr__método 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 dataclassesusted 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 clase también puede generar métodos de comparación ( __lt__, __gt__, etc.) y la inmutabilidad mango.

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

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

Si desea ver algunos ejemplos, recientemente utilicé dataclassesvarias de mis soluciones de Advent of Code , consulte las soluciones para el día 7 , día 8 , día 11 y día 20 .

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


2
En el primer ejemplo, ¿oculta intencionalmente miembros de clase con miembros de instancia con los mismos nombres? Por favor, ayuda a entender este idioma.
VladimirLenin

44
@VladimirLenin: no hay atributos de clase, solo hay anotaciones de tipo. Ver PEP 526 , específicamente la sección de anotaciones de clase e instancia variable .
Martijn Pieters

1
@Bananach: @dataclassgenera aproximadamente el mismo __init__método, con un quantity_on_handargumento de palabra clave con valor predeterminado. Cuando crea una instancia, establecerá el quantity_on_handatributo de instancia, siempre. Entonces, mi primer ejemplo que no es de clase de datos usa el mismo patrón para hacer eco de lo que hará el código generado por la clase de datos.
Martijn Pieters

1
@Bananach: así que en el primer ejemplo, podríamos omitir establecer un atributo de instancia y no sombrear el atributo de clase, es redundante configurarlo de todos modos en ese sentido, pero las clases de datos lo establecen.
Martijn Pieters

1
@ user2853437 su caso de uso no es realmente compatible con las clases de datos; quizás sería mejor usar el primo más grande de las clases de datos , attrs . Ese proyecto admite convertidores por campo que le permiten normalizar los valores de campo. Si desea seguir con las clases de datos, entonces sí, normalice el __post_init__método.
Martijn Pieters

62

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, de atributos, como clases namedtupley 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 (ver ejemplos a continuación).
  • namedtuple : tienes puntos, acceso a atributos como una namedtupleo 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

Esta es una descripción general de las características de la clase de datos (TL; DR? Consulte la tabla de resumen en la siguiente sección).

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

Estos valores predeterminados se proporcionan configurando automáticamente las siguientes palabras clave 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 están establecidas 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:) < > <= >=, de manera similar a functools.total_orderinglas 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=Trueo 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:

Desempacando

@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, ValueErrorse plantea a.

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__               |
|                      |                      |                                                    |                                         |
| Unpacking+           |  -                   |  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 es necesario y, por lo tanto, no se implementa .


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, recursivamente :

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

Limitaciones


Referencias

  • Charla de R. Hettinger sobre Dataclasses: el generador de código para finalizar todos los generadores de código
  • Charla de T. Hunner sobre Clases más fáciles: Clases de Python sin todo lo rudo
  • Documentación de Python sobre detalles de hashing
  • Guía de Real Python sobre La guía definitiva para las clases de datos en Python 3.7
  • La publicación de 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

2

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 la instancia, 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 @dataclassgenerador añade métodos de la clase que desea lo contrario define a sí mismo como __repr__, __init__, __lt__, y __gt__.


2

Considera esta clase simple Foo

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

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

ingrese la descripción de la imagen aquí

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

ingrese la descripción de la imagen aquí

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.