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

Resuelto kingJulian asked hace 6 años • 4 respuestas

PEP 557 introduce clases de datos en la biblioteca estándar de Python. Dice que al aplicar el @dataclassdecorador que se muestra a continuación, generará "entre otras cosas, un __init__()".

from dataclasses import dataclass

@dataclass
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

También dice que las clases de datos son "tuplas nombradas mutables con valor predeterminado", pero no entiendo qué significa esto, ni en qué se diferencian las clases de datos de las clases comunes.

¿Qué son las clases de datos y cuándo es mejor utilizarlas?

kingJulian avatar Dec 24 '17 02:12 kingJulian
Aceptado

Las clases de datos son simplemente clases normales que están orientadas a almacenar el estado, en lugar de contener mucha lógica. Cada vez que creas una clase que consta principalmente de atributos, creas una clase de datos.

Lo que hace el dataclassesmódulo es facilitar la creación de clases de datos. Se ocupa de muchos textos repetitivos por usted.

Esto es especialmente útil cuando su clase de datos debe ser hash; porque esto requiere un __hash__método además de un __eq__método. Si agrega un __repr__método personalizado para facilitar la depuración, 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 dataclassespuedes 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

(Ejemplo basado en el ejemplo de PEP ).

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

namedtupleLas clases también son clases de datos, pero son inmutables de forma predeterminada (además de ser secuencias). dataclassesson mucho más flexibles en este sentido y pueden estructurarse fácilmente de manera que puedan cumplir el mismo rol que una namedtupleclase .

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

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

Si desea utilizar dataclassesel módulo en versiones de Python <3.7, puede instalar el módulo respaldado (requiere 3.6) o utilizar el attrsproyecto mencionado anteriormente.

Martijn Pieters avatar Dec 23 '2017 19:12 Martijn Pieters

Descripción general

La cuestión 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 : genera código repetitivo; puede optar por implementar métodos especiales en una clase normal 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 a atributos con puntos, como clases namedtupley otros .

"tuplas con nombres mutables con valores predeterminados"

Esto es lo que significa la última frase:

  • mutable : de forma predeterminada, los atributos de clase de datos se pueden reasignar. Opcionalmente, puede hacerlos inmutables (consulte los ejemplos a continuación).
  • nametuple : tiene acceso a atributos punteados como una clase regular namedtupleo una clase regular.
  • predeterminado : puede asignar valores predeterminados a los atributos.

En comparación con las clases comunes, principalmente ahorra en escribir código repetitivo.


Características

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 obtienes de forma predeterminada 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 estableciendo automáticamente las siguientes palabras clave en True:

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

¿Qué puedes activar?

Hay funciones adicionales disponibles si se configuran las palabras clave adecuadas en True.

Orden

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

Los métodos de pedido ahora están implementados (operadores de sobrecarga:) < > <= >=, de manera similar a functools.total_orderinglas pruebas de igualdad más estrictas.

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 ni asignar atributos.

En general, el objeto es hash si cualquiera de los dos unsafe_hash=Trueo frozen=True.

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

Mejoramiento

@dataclasses.dataclass(slots=True)              # py310+
class SlottedColor:
    #__slots__ = ["r", "b", "g"]                # alternative
    r : int
    g : int
    b : int

El tamaño del objeto ahora se reduce:

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

slots=Truefue agregado en Python 3.10. (Gracias @ajskateboarder).

En algunas circunstancias, slots=True/ __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 eleva a. Si __slot__ya existe, slots=Trueprovocará un archivo TypeError.

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

Vea más sobre los argumentos agregados en Python 3.10+ : match_args, kw_only, slots.weakref_slot

lo que no entiendes

Para obtener las siguientes funciones, se deben implementar métodos especiales manualmente:

Desembalaje

@dataclasses.dataclass
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

    def __iter__(self):
        yield from dataclasses.astuple(self)

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__               |
| Optimization         |  slots               |  sys.getsizeof(SlottedColor) -> 888                |  __slots__                              |
|                      |                      |                                                    |                                         |
| Unpacking+           |  -                   |  r, g, b = Color()                                 |  __iter__                               |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+

* __ne__ no es necesario y por lo tanto no se implementa .

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


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, de forma recursiva :

>>> 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
  • Trabajar con clases de datos anidadas puede resultar complicado

Referencias

  • Charla de R. Hettinger sobre clases de datos: el generador de código para acabar con todos los generadores de código
  • Charla de T. Hunner sobre clases más fáciles: clases de Python sin todo el cruft
  • Documentación de Python sobre detalles de hash
  • Guía de Real Python sobre la guía definitiva para clases de datos en Python 3.7
  • Publicación del blog de A. Shaw sobre un breve recorrido por las clases de datos de Python 3.7
  • Repositorio github de E. Smith sobre clases de datos
pylang avatar Sep 11 '2018 19:09 pylang

De la especificación PEP :

Se proporciona un decorador de clases que inspecciona una definición de clase en busca de variables con anotaciones de tipo como se define en PEP 526, "Sintaxis para anotaciones de variables". En este documento, dichas variables se denominan campos. Usando estos campos, el decorador agrega definiciones de métodos generados a la clase para admitir la inicialización de instancias, una repetición, métodos de comparación y, opcionalmente, otros métodos como se describe en la sección Especificación. Esta 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 agrega métodos a la clase que de otro modo tendrías que definir tú mismo como __repr__, __init__, __lt__y __gt__.

Mahmoud Hanafy avatar Dec 23 '2017 19:12 Mahmoud Hanafy

Considere esta clase simpleFoo

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 en el derecho 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 comparar.

ingrese la descripción de la imagen aquí

prosti avatar May 23 '2019 19:05 prosti