Comment utiliser les classes de données Python

Tout en Python est un objet, du moins c'est ce que dit le dicton. Si vous souhaitez créer vos propres objets personnalisés, avec leurs propres propriétés et méthodes, vous utilisez l' classobjet de Python pour y parvenir. Mais créer des classes en Python signifie parfois écrire des charges de code standard répétitif pour configurer l'instance de classe à partir des paramètres qui lui sont passés ou pour créer des fonctions courantes telles que des opérateurs de comparaison.

Les classes de données, introduites dans Python 3.7 (et rétroportées vers Python 3.6), fournissent un moyen pratique de rendre les classes moins verbeuses. La plupart des choses courantes que vous faites dans une classe, comme l'instanciation de propriétés à partir des arguments passés à la classe, peuvent être réduites à quelques instructions de base.

Exemple de classe de données Python

Voici un exemple simple de classe conventionnelle en Python:

livre de classe:

'' 'Objet de suivi des livres physiques dans une collection.' ''

def __init __ (soi, nom: str, poids: float, id_tagère: int = 0):

self.name = nom

self.weight = poids # en grammes, pour calculer l'expédition

self.shelf_id = id_étagère

def __repr __ (soi):

return (f "Book (name = {self.name! r},

poids = {self.weight! r}, étagère_id = {self.shelf_id! r}) ")

Le plus gros casse-tête ici est la façon dont chacun des arguments passés  __init__ doit être copié dans les propriétés de l'objet. Ce n'est pas si mauvais si vous êtes seulement traiter  Book, mais si vous avez à traiter  BookshelfLibraryWarehouseet ainsi de suite? De plus, plus vous devez saisir de code à la main, plus vous avez de chances de faire une erreur.

Voici la même classe Python, implémentée comme une classe de données Python:

from dataclasses import dataclass @dataclass class Book: '' 'Objet de suivi des livres physiques dans une collection.' '' name: str weight: float tag_id: int = 0 

Lorsque vous spécifiez des propriétés, appelées  champs,  dans une classe de données,  @dataclass génère automatiquement tout le code nécessaire pour les initialiser. Il préserve également les informations de type pour chaque propriété, donc si vous utilisez un linter de code comme  mypy, cela garantira que vous fournissez les bons types de variables au constructeur de classe.

Une autre chose à  @dataclass faire dans les coulisses est de créer automatiquement du code pour un certain nombre de méthodes dunder courantes dans la classe. Dans la classe conventionnelle ci-dessus, nous avons dû créer la nôtre  __repr__. Dans la classe de données, cela n'est pas nécessaire; @dataclass génère le  __repr__ pour vous.

Une fois qu'une classe de données est créée, elle est fonctionnellement identique à une classe régulière. Il n'y a aucune pénalité de performances pour l'utilisation d'une classe de données, à l'exception de la surcharge minimale du décorateur lors de la déclaration de la définition de classe.

Personnalisez les champs de classe de données Python avec la  field fonction

Le fonctionnement par défaut des classes de données devrait convenir à la majorité des cas d'utilisation. Parfois, cependant, vous devez affiner la façon dont les champs de votre classe de données sont initialisés. Pour ce faire, vous pouvez utiliser la  field fonction.

from dataclasses import dataclass, field from typing import List @dataclass class Book: '' 'Objet de suivi des livres physiques dans une collection.' '' name: str condition: str = field (compare = False) weight: float = field (default = 0.0, repr = False) id_étagère: int = 0 chapitres: Liste [str] = champ (default_factory = liste) 

Lorsque vous définissez une valeur par défaut sur une instance de  field, cela change la façon dont le champ est configuré en fonction des paramètres que vous donnez  field. Voici les options les plus couramment utilisées pour field (il y en a d'autres):

  • default: Définit la valeur par défaut du champ. Vous devez utiliser defaultsi vous a) utilisez  field pour modifier tout autre paramètre pour le champ, et b) vous voulez définir une valeur par défaut sur le champ en plus de cela. Dans ce cas , nous utilisons  default pour ensemble  weight à  0.0.
  • default_factory: Fournit le nom d'une fonction, qui ne prend aucun paramètre, qui renvoie un objet qui servira de valeur par défaut pour le champ. Dans ce cas, nous voulons  chapters être une liste vide.
  • repr: Par défaut ( True), contrôle si le champ en question apparaît dans __repr__ le fichier généré automatiquement  pour la classe de données. Dans ce cas, nous ne voulons pas que le poids du livre soit indiqué dans le  __repr__, donc nous l'  repr=False omettons.
  • compare: Par défaut ( True), inclut le champ dans les méthodes de comparaison générées automatiquement pour la classe de données. Ici, nous ne voulons  condition pas être utilisés dans le cadre de la comparaison de deux livres, alors nous nous fixons  compare=False.

Notez que nous avons dû ajuster l'ordre des champs pour que les champs non par défaut viennent en premier.

Utilisez  __post_init__ pour contrôler l'initialisation de la classe de données Python

À ce stade, vous vous demandez probablement: si la  __init__ méthode d'une classe de données est générée automatiquement, comment puis-je contrôler le processus d'initialisation pour apporter des modifications plus fines?

Entrez la  __post_init__ méthode. Si vous incluez la  __post_init__méthode dans votre définition de classe de données, vous pouvez fournir des instructions pour modifier des champs ou d'autres données d'instance.

from dataclasses import dataclass, field from typing import List @dataclass class Book: '' 'Objet pour le suivi des livres physiques dans une collection.' '' name: str weight: float = field (default = 0.0, repr = False) shel_id: int = field (init = False) chapitres: List [str] = field (default_factory = list) condition: str = field (default = "Good", compare = False) def __post_init __ (self): if self.condition == "Rejeté ": self.shelf_id = Aucun autre: self.shelf_id = 0 

Dans cet exemple, nous avons créé une  __post_init__ méthode pour définir shelf_id à  None si l'état du livre est initialisé  "Discarded". Remarquez comment  field initialiser  shelf_id, et passe  init comme  False à  field. Cela signifie  shelf_id qu'il ne sera pas initialisé dans  __init__.

Utilisez  InitVar pour contrôler l'initialisation de la classe de données Python

Une autre façon de personnaliser la configuration de la classe de données Python consiste à utiliser le  InitVar type. Cela vous permet de spécifier un champ qui sera passé à  __init__ , puis à  __post_init__, mais qui ne sera pas stocké dans l'instance de classe.

En utilisant InitVar, vous pouvez prendre des paramètres lors de la configuration de la classe de données qui ne sont utilisés que pendant l'initialisation. Un exemple:

from dataclasses import dataclass, field, InitVar from typing import List @dataclass class Book: '' 'Objet de suivi des livres physiques dans une collection.' '' name: str condition: InitVar [str] = None weight: float = field (default = 0.0, repr = False) clay_id: int = field (init = False) chapitres: List [str] = field (default_factory = list) def __post_init __ (self, condition): if condition == "Discarded": self.shelf_id = Aucun autre: self.shelf_id = 0 

La définition du type d'un champ sur  InitVar (avec son sous-type étant le type de champ réel) indique de  @dataclass ne pas transformer ce champ en un champ de classe de données, mais de transmettre les données en  __post_init__ tant qu'argument.

Dans cette version de notre  Book classe, nous ne stockons pas en  condition tant que champ dans l'instance de classe. Nous n'utilisons que conditionpendant la phase d'initialisation. Si nous trouvons que cela a  condition été défini sur  "Discarded", nous définissons  shelf_id sur  None - mais nous ne stockons pas  condition dans l'instance de classe.

Quand utiliser les classes de données Python - et quand ne pas les utiliser

Un scénario courant d'utilisation des classes de données consiste à remplacer le namedtuple. Les classes de données offrent les mêmes comportements et plus encore, et elles peuvent être rendues immuables (comme le sont les multiplets nommés) en les utilisant simplement  @dataclass(frozen=True) comme décorateur.

Another possible use case is replacing nested dictionaries, which can be clumsy to work with, with nested instances of dataclasses. If you have a dataclass Library, with a list property shelves, you could use a dataclass ReadingRoom to populate that list, and then add methods to make it easy to access nested items (e.g., a book on a shelf in a particular room).

But not every Python class needs to be a dataclass. If you’re creating a class mainly as a way to group together a bunch of static methods, rather than as a container for data, you don’t need to make it a dataclass. For instance, a common pattern with parsers is to have a class that takes in an abstract syntax tree, walks the tree, and dispatches calls to different methods in the class based on the node type. Because the parser class has very little data of its own, a dataclass isn’t useful here.

How to do more with Python

  • Get started with async in Python
  • How to use asyncio in Python
  • How to use PyInstaller to create Python executables
  • Cython tutorial: How to speed up Python
  • How to install Python the smart way
  • How to manage Python projects with Poetry
  • How to manage Python projects with Pipenv
  • Virtualenv et venv: les environnements virtuels Python expliqués
  • Python Virtualenv et Venv à faire et à ne pas faire
  • Explication des threads et sous-processus Python
  • Comment utiliser le débogueur Python
  • Comment utiliser timeit pour profiler le code Python
  • Comment utiliser cProfile pour profiler le code Python
  • Comment convertir Python en JavaScript (et inversement)