312 votes

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 de Python.

Ils font appel à la @dataclass et ils sont censés être des "namedtuples 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 ?

9 votes

Compte tenu de l'ampleur du contenu du PEP, que voulez-vous savoir d'autre ? namedtuple sont immuables et ne peuvent pas avoir de valeurs par défaut pour les attributs, alors que les classes de données sont mutables et peuvent en avoir.

75 votes

@jonrsharpe Il me semble raisonnable qu'il y ait un fil de discussion stackoverflow sur le sujet. Stackoverflow est censé être une encyclopédie sous forme de questions-réponses, non ? La réponse n'est jamais "regardez simplement sur cet autre site web". Il ne devrait pas y avoir de downvotes ici.

40 votes

Il y a cinq fils de discussion sur la façon d'ajouter un élément à une liste. Une question sur @dataclass ne provoquera pas la désintégration du site.

344voto

Martijn Pieters Points 271458

Les classes de données sont des classes ordinaires destinées à stocker un état plutôt qu'à contenir beaucoup de logique. Chaque fois que vous créez une classe qui se compose principalement d'attributs, vous créez une classe de données.

Qu'est-ce que le dataclasses module fait est de le rendre plus facile pour créer des classes de données. Il s'occupe d'une grande partie de la paperasse pour vous.

C'est particulièrement utile lorsque votre classe de données doit être hachable ; car cela nécessite un fichier __hash__ ainsi qu'une méthode __eq__ méthode. Si vous ajoutez une __repr__ pour faciliter le débogage, qui peut devenir assez verbeux :

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 de gérer l'immuabilité.

namedtuple sont également des classes de données, mais sont immuables par défaut (ainsi que des séquences). dataclasses sont beaucoup plus flexibles à cet égard, et peuvent facilement être structurés de manière à ce qu'ils puissent remplissent le même rôle qu'un namedtuple classe .

Le PEP a été inspiré par le attrs projet qui peut faire encore plus (y compris les créneaux, 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 L'avènement du code voir les solutions pour 7ème jour , jour 8 , 11ème jour y 20ème jour .

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

3 votes

Dans le premier exemple, cachez-vous intentionnellement des membres de classe avec des membres d'instance de même nom ? Veuillez m'aider à comprendre cet idiome.

10 votes

@VladimirLenin : il n'y a pas d'attributs de classe, il n'y a que des annotations de type. Voir PEP 526 et plus particulièrement le Annotations des classes et des variables d'instance section .

0 votes

@VladimirLenin : la = 0 valeur par défaut pour quantity_on_hand est redondant dans le premier exemple et n'est là que pour faire écho à l'élément dataclasses exemple.

185voto

pylang Points 12013

Vue d'ensemble

La question a été abordée. Cependant, cette réponse ajoute quelques exemples pratiques pour aider à 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 codes Vous pouvez choisir d'implémenter les méthodes spéciales dans une classe ordinaire ou de les faire implémenter automatiquement par une classe de données.
  2. conteneurs de données les structures qui contiennent des données (par exemple, les tuples et les dicts), souvent avec des accès en pointillés et des attributs tels que classes, namedtuple et autres .

"namedtuples mutables avec défaut[s]"

Voici ce que signifie cette dernière phrase :

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

Par rapport aux classes communes, vous économisez surtout la saisie de code passe-partout.


Caractéristiques

Il s'agit d'un aperçu des caractéristiques des classes de données (TL;DR ? Voir le tableau récapitulatif dans la section suivante).

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

Ces valeurs par défaut sont fournies en définissant automatiquement les mots clés suivants à True :

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

Ce que vous pouvez activer

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

Commandez

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

Les méthodes d'ordonnancement sont maintenant implémentées (opérateurs de surcharge) : < > <= >= ), comme dans le cas de functools.total_ordering avec des tests d'égalité plus forts.

Hachable, Mutable

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

Bien que l'objet soit potentiellement mutable (ce qui peut être indésirable), un hachage est mis en œuvre.

Hachable, Immuable

@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 hachable si soit unsafe_hash=True o frozen=True .

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

Ce que vous ne comprenez pas

Pour obtenir les caractéristiques suivantes, des méthodes spéciales doivent être mises en œuvre manuellement :

Déballage

@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__ améliore également la vitesse de création des instances et d'accès aux attributs. De plus, les slots n'autorisent pas les affectations par défaut ; sinon, une instance de ValueError est soulevée.

Pour en savoir plus sur les machines à sous, voir article de blog .


Tableau récapitulatif

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

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

<sup>* </sup><code>ne</code> n'est pas nécessaire et donc <a href="https://github.com/ericvsmith/dataclasses/blob/d4c87c663feb0617fd8c7e6fe0a58882c4ccd468/dataclasses.py#L882" rel="noreferrer">non appliqué </a>.


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

Convertit 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

  • R. Hettinger parler en Dataclasses : Le générateur de code pour mettre fin à tous les générateurs de code
  • T. Hunner parler en Des cours plus faciles : Les classes Python sans tout le superflu
  • Python documentation sur les détails du hachage
  • Les vrais Python guide en Le guide ultime des classes de données en Python 3.7
  • A. Shaw article de blog en Un bref tour d'horizon des classes de données de Python 3.7
  • E. Smith Dépôt Github en classes de données

2 votes

Je donnerais deux likes si c'était possible. Très belle réponse @pylang. Je vous tire mon chapeau Monsieur/Madame ;)

3 votes

Cette réponse est bien meilleure que celle qui a été acceptée. Bravo !

8 votes

J'aime vraiment ces réponses étendues de la longueur d'un micro-blog. Bien formatées, divisées en rubriques digestes, en extraits de code et en sections de références.

15voto

Messa Points 5988

À propos, Raymond Hettinger (développeur principal de Python) a fait une excellente présentation à la PyCon 2018 :

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

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

comparison

3voto

Mahmoud Hossam Points 1490

De la Spécification PEP :

Un décorateur de classe est fourni, qui inspecte la définition d'une classe à la recherche de variables avec des annotations de type telles que définies dans le PEP 526, "Syntax for Variable Annotations". Dans ce document, de telles variables sont appelées champs. En utilisant ces champs, le décorateur ajoute la méthode générée générées à la classe pour supporter l'initialisation de l'instance, une repr, les méthodes de comparaison et, éventuellement, d'autres méthodes décrites dans le document intitulé Spécification. Une telle classe s'appelle une classe de données, mais il n'y a vraiment rien de spécial à propos de la c ajoute des méthodes générées à la classe et renvoie la même classe qu'on lui a donnée.

En @dataclass ajoute à la classe des méthodes que vous auriez autrement définies vous-même, par exemple __repr__ , __init__ , __lt__ y __gt__ .

3voto

prosti Points 4630

Considérons cette classe simple Foo

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

Voici le dir() comparaison intégrée. Sur le côté gauche se trouve le Foo sans le décorateur @dataclass, et à droite, avec le décorateur @dataclass.

enter image description here

Voici une autre différence, après avoir utilisé le inspect à titre de comparaison.

enter image description here

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X