web-dev-qa-db-fra.com

Que sont les classes de données et en quoi sont-elles différentes des classes communes?

Avec PEP 557 , les classes de données sont introduites dans la bibliothèque standard python.

Ils utilisent le décorateur @dataclass et ils sont supposés être des "noms nommés mutables avec défaut" mais je ne suis pas vraiment sûr de comprendre ce que cela signifie réellement et en quoi ils sont différents des classes communes.

Que sont exactement les classes de données python et quand est-il préférable de les utiliser?

64
kingJulian

Les classes de données ne sont que des classes régulières orientées vers l'état de stockage. Elles contiennent plus que beaucoup de logique. Chaque fois que vous créez une classe composée principalement d'attributs, vous avez créé une classe de données.

Le module dataclasses facilite la création de la création de classes de données. Il prend soin de beaucoup de plaque de la chaudière pour vous.

Ceci est particulièrement important lorsque votre classe de données doit être lavable; cela nécessite une méthode __hash__ ainsi qu'une méthode __eq__. Si vous ajoutez une méthode personnalisée __repr__ pour faciliter le débogage, cela peut devenir assez détaillé:

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))

Avec dataclasses, vous pouvez le réduire à:

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

Le même décorateur de classe peut également générer des méthodes de comparaison (__lt__, __gt__, etc.) et gérer l’immuabilité.

Les classes namedtuple sont aussi des classes de données, mais sont immuables par défaut (elles sont également des séquences). Les dataclasses sont beaucoup plus souples à cet égard et peuvent facilement être structurés de manière à pouvoir remplir le même rôle qu'une classe namedtuple .

Le PEP a été inspiré par le attrs project , qui peut faire encore plus (y compris les créneaux horaires, les validateurs, les convertisseurs, les métadonnées, etc.).

Si vous voulez voir quelques exemples, j’ai récemment utilisé dataclasses pour plusieurs de mes solutions Advent of Code , voir les solutions pour jour 7 , jour 8 , jour 11 et jour 2 .

Si vous voulez utiliser le module dataclasses dans Python versions <3.7, vous pouvez installer le module avec port de retour (nécessite 3.6) ou utiliser le projet attrs. mentionné ci-dessus.

79
Martijn Pieters

Vue d'ensemble

La question a été abordée. Cependant, cette réponse ajoute quelques exemples pratiques facilitant la compréhension de base des classes de données.

Que sont exactement les classes de données python et quand est-il préférable de les utiliser?

  1. générateurs de code : génère un code passe-partout; vous pouvez choisir d'implémenter des méthodes spéciales dans une classe standard ou qu'une classe de données les implémente automatiquement.
  2. conteneurs de données : structures contenant des données (par exemple, des tuples et des dictons), souvent avec un accès en attribut, telles que classes, namedtuple et autres .

"Tubes nommés mutables avec la valeur par défaut [s]"

Voici ce que cette dernière phrase signifie:

  • mutable : par défaut, les attributs de classe de données peuvent être réaffectés. Vous pouvez éventuellement les rendre immuables (voir Exemples ci-dessous).
  • namedtuple : vous avez un accès pointillé aux attributs, comme un namedtuple ou une classe normale.
  • default : vous pouvez affecter des valeurs par défaut aux attributs.

Par rapport aux classes courantes, vous économisez principalement sur la saisie du code standard.


Caractéristiques

Voici un aperçu des fonctionnalités de la classe de données (voir les exemples dans le tableau récapitulatif).

Ce que vous obtenez

Voici les fonctionnalités que vous obtenez par défaut des classes de données.

Attributs + Représentation + Comparaison

_import dataclasses


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

Les valeurs par défaut suivantes sont automatiquement définies sur True:

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

Ce que vous pouvez allumer

Des fonctionnalités supplémentaires sont disponibles si les mots-clés appropriés sont définis sur True.

Ordre

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

Les méthodes de classement sont maintenant implémentées (opérateurs de surcharge: _< > <= >=_), de la même manière que functools.total_ordering avec des tests d'égalité plus stricts.

Hashable, Mutable

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

Bien que l'objet soit potentiellement modifiable (éventuellement indésirable), un hachage est implémenté.

Hashable, Immutable

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

Un hachage est maintenant implémenté et la modification de l'objet ou l'attribution d'attributs est interdite.

Globalement, l'objet est obligatoire si _unsafe_hash=True_ ou _frozen=True_.

Voir aussi l'original table logique de hachage avec plus de détails.

Ce que vous n'obtenez pas

Pour obtenir les fonctionnalités suivantes, des méthodes spéciales doivent être implémentées manuellement:

Déballable

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

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

Optimisation

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

La taille de l'objet est maintenant réduite:

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

Dans certaines circonstances, ___slots___ accélère également la création d'instances et l'accès aux attributs. De plus, les emplacements ne permettent pas les assignations par défaut; sinon, une ValueError est levée.

Voir plus sur les créneaux horaires dans ce article de blog .


Sommaire

_+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
|       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__                              |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
_

+Ces méthodes ne sont pas générées automatiquement et nécessitent une implémentation manuelle dans une classe de données.

* ___ne___ est non implémenté .


Caractéristiques supplémentaires

Post-initialisation

_@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)
_

Héritage

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

Conversions

Convertir une classe de données en un tuple ou un dict, récursivement :

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

Limites


Références

  • Les conversation sur Dataclasses de R. Hettinger: Le générateur de code pour mettre fin à tous les générateurs de code
  • T. Hunner's conversation sur Classes plus faciles: Python Classes sans tout le bricolage
  • Python documentation sur les détails de hachage
  • Real Python's guide on Le guide ultime des classes de données dans Python 3.7
  • A. Shaw's article de blog sur Une brève visite à Python 3.7 classes de données
  • E. Smith's référentiel github sur dataclasses
21
pylang

Btw. Raymond Hettinger (développeur principal de Python) a eu une excellente conférence à la conférence PyCon 2018:

https://www.youtube.com/watch?v=T-TwcmT6Rcw&t=139

Les diapositives sont ici: https://Twitter.com/raymondh/status/995693882812915712

comparison

13
Messa

De la spécification PEP :

Un décorateur de classe est fourni pour inspecter une définition de classe pour les variables avec des annotations de type telles que définies dans PEP 526, "Syntaxe pour les annotations de variable". Dans ce document, ces variables s'appellent des champs. À l'aide de ces champs, le décorateur ajoute les définitions de méthode générées à la classe pour prendre en charge l'initialisation d'instance, une repr, les méthodes de comparaison et éventuellement d'autres méthodes, comme décrit dans la section Spécification. Une telle classe s'appelle une classe de données, mais elle n'a vraiment rien de spécial: le décorateur ajoute les méthodes générées à la classe et retourne la même classe qui lui a été donnée.

Le générateur @dataclass ajoute à la classe les méthodes que vous définiriez autrement, comme __repr__, __init__, __lt__ et __gt__.

2
Mahmoud Hossam

Considérez cette classe simple Foo

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

Voici la comparaison intégrée dir(). A gauche, Foo sans le décorateur @dataclass et à droite, avec le décorateur @dataclass.

enter image description here

Voici un autre diff, après avoir utilisé le module inspect à des fins de comparaison.

enter image description here

0
prosti