Redéfinition des méthodes spéciales

Intermediate

This tutorial is from open-source community. Access the source code

Introduction

Dans ce laboratoire, vous apprendrez à personnaliser le comportement des objets en redéfinissant des méthodes spéciales. Vous allez également modifier la manière dont les objets définis par l'utilisateur sont affichés et rendre les objets comparables.

De plus, vous apprendrez à créer un gestionnaire de contexte (context manager). Le fichier à modifier dans ce laboratoire est stock.py.

Ceci est un Guided Lab, qui fournit des instructions étape par étape pour vous aider à apprendre et à pratiquer. Suivez attentivement les instructions pour compléter chaque étape et acquérir une expérience pratique. Les données historiques montrent que c'est un laboratoire de niveau intermédiaire avec un taux de réussite de 74%. Il a reçu un taux d'avis positifs de 92% de la part des apprenants.

Amélioration de la représentation des objets avec __repr__

En Python, les objets peuvent être représentés sous forme de chaînes de caractères de deux manières différentes. Ces représentations servent à des fins différentes et sont utiles dans diverses situations.

Le premier type est la représentation sous forme de chaîne de caractères. Elle est créée par la fonction str(), qui est appelée automatiquement lorsque vous utilisez la fonction print(). La représentation sous forme de chaîne de caractères est conçue pour être lisible par l'homme. Elle présente l'objet dans un format que nous pouvons facilement comprendre et interpréter.

Le deuxième type est la représentation sous forme de code. Elle est générée par la fonction repr(). La représentation sous forme de code montre le code que vous devriez écrire pour recréer l'objet. Elle vise plus à fournir un moyen précis et non ambigu de représenter l'objet dans le code.

Regardons un exemple concret en utilisant la classe date intégrée à Python. Cela vous aidera à voir la différence entre les représentations sous forme de chaîne de caractères et de code en action.

>>> from datetime import date
>>> d = date(2008, 7, 5)
>>> print(d)              ## Uses str()
2008-07-05
>>> d                     ## Uses repr()
datetime.date(2008, 7, 5)

Dans cet exemple, lorsque nous utilisons print(d), Python appelle la fonction str() sur l'objet date d, et nous obtenons une date lisible par l'homme au format AAAA-MM-JJ. Lorsque nous tapons simplement d dans le shell interactif, Python appelle la fonction repr(), et nous voyons le code nécessaire pour recréer l'objet date.

Vous pouvez obtenir explicitement la chaîne de caractères retournée par repr() de diverses manières. Voici quelques exemples :

>>> print('The date is', repr(d))
The date is datetime.date(2008, 7, 5)
>>> print(f'The date is {d!r}')
The date is datetime.date(2008, 7, 5)
>>> print('The date is %r' % d)
The date is datetime.date(2008, 7, 5)

Maintenant, appliquons ce concept à notre classe Stock. Nous allons améliorer la classe en implémentant la méthode __repr__. Cette méthode spéciale est appelée par Python lorsqu'il a besoin de la représentation sous forme de code d'un objet.

Pour ce faire, ouvrez le fichier stock.py dans votre éditeur. Ensuite, ajoutez la méthode __repr__ à la classe Stock. La méthode __repr__ doit retourner une chaîne de caractères qui montre le code nécessaire pour recréer l'objet Stock.

def __repr__(self):
    return f"Stock('{self.name}', {self.shares}, {self.price})"

Après avoir ajouté la méthode __repr__, votre classe Stock complète devrait maintenant ressembler à ceci :

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    def cost(self):
        return self.shares * self.price

    def sell(self, shares):
        self.shares -= shares

    def __repr__(self):
        return f"Stock('{self.name}', {self.shares}, {self.price})"

Maintenant, testons notre classe Stock modifiée. Ouvrez un shell interactif Python en exécutant la commande suivante dans votre terminal :

python3

Une fois le shell interactif ouvert, essayez les commandes suivantes :

>>> import stock
>>> goog = stock.Stock('GOOG', 100, 490.10)
>>> goog
Stock('GOOG', 100, 490.1)

Vous pouvez également voir comment la méthode __repr__ fonctionne avec un portefeuille d'actions. Voici un exemple :

>>> import reader
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> portfolio
[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), Stock('IBM', 100, 70.44)]

Comme vous pouvez le voir, la méthode __repr__ a rendu nos objets Stock beaucoup plus informatifs lorsqu'ils sont affichés dans le shell interactif ou le débogueur. Elle montre maintenant le code nécessaire pour recréer chaque objet, ce qui est très utile pour le débogage et la compréhension de l'état des objets.

Une fois que vous avez terminé les tests, vous pouvez quitter l'interpréteur Python en exécutant la commande suivante :

>>> exit()

Rendre les objets comparables avec __eq__

En Python, lorsque vous utilisez l'opérateur == pour comparer deux objets, Python appelle en réalité la méthode spéciale __eq__. Par défaut, cette méthode compare les identités des objets, ce qui signifie qu'elle vérifie si ils sont stockés à la même adresse mémoire, plutôt que de comparer leur contenu.

Regardons un exemple. Supposons que nous ayons une classe Stock, et que nous créons deux objets Stock avec les mêmes valeurs. Ensuite, nous essayons de les comparer en utilisant l'opérateur ==. Voici comment vous pouvez le faire dans l'interpréteur Python :

Tout d'abord, lancez l'interpréteur Python en exécutant la commande suivante dans votre terminal :

python3

Ensuite, dans l'interpréteur Python, exécutez le code suivant :

>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
False

Comme vous pouvez le voir, même si les deux objets Stock a et b ont les mêmes valeurs pour leurs attributs (name, shares et price), Python les considère comme des objets différents car ils sont stockés à des emplacements mémoire différents.

Pour résoudre ce problème, nous pouvons implémenter la méthode __eq__ dans notre classe Stock. Cette méthode sera appelée chaque fois que l'opérateur == est utilisé sur des objets de la classe Stock.

Maintenant, réouvrez le fichier stock.py. À l'intérieur de la classe Stock, ajoutez la méthode __eq__ suivante :

def __eq__(self, other):
    return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
                                         (other.name, other.shares, other.price))

Analysons ce que fait cette méthode :

  1. Tout d'abord, elle utilise la fonction isinstance pour vérifier si l'objet other est une instance de la classe Stock. Cela est important car nous voulons seulement comparer des objets Stock avec d'autres objets Stock.
  2. Si other est un objet Stock, elle compare ensuite les attributs (name, shares et price) de l'objet self et de l'objet other.
  3. Elle retourne True seulement si les deux objets sont des instances de Stock et que leurs attributs sont identiques.

Après avoir ajouté la méthode __eq__, votre classe Stock complète devrait ressembler à ceci :

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    def cost(self):
        return self.shares * self.price

    def sell(self, shares):
        self.shares -= shares

    def __repr__(self):
        return f"Stock('{self.name}', {self.shares}, {self.price})"

    def __eq__(self, other):
        return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
                                             (other.name, other.shares, other.price))

Maintenant, testons notre classe Stock améliorée. Relancez l'interpréteur Python :

python3

Ensuite, exécutez le code suivant dans l'interpréteur Python :

>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
True
>>> c = stock.Stock('GOOG', 200, 490.1)
>>> a == c
False

Parfait ! Maintenant, nos objets Stock peuvent être correctement comparés en fonction de leur contenu, plutôt que de leur adresse mémoire.

La vérification isinstance dans la méthode __eq__ est cruciale. Elle garantit que nous ne comparons que des objets Stock. Si nous n'avions pas cette vérification, comparer un objet Stock avec quelque chose qui n'est pas un objet Stock pourrait générer des erreurs.

Une fois que vous avez terminé les tests, vous pouvez quitter l'interpréteur Python en exécutant la commande suivante :

>>> exit()

Création d'un gestionnaire de contexte

Un gestionnaire de contexte est un type spécial d'objet en Python. En Python, les objets peuvent avoir différentes méthodes qui définissent leur comportement. Un gestionnaire de contexte définit spécifiquement deux méthodes importantes : __enter__ et __exit__. Ces méthodes fonctionnent en conjonction avec l'instruction with. L'instruction with est utilisée pour configurer un contexte spécifique pour un bloc de code. Imaginez cela comme la création d'un petit environnement où certaines choses se produisent, et lorsque le bloc de code est terminé, le gestionnaire de contexte s'occupe du nettoyage.

Dans cette étape, nous allons créer un gestionnaire de contexte qui a une fonction très utile. Il redirigera temporairement la sortie standard (sys.stdout). La sortie standard est là où la sortie normale de votre programme Python va, généralement la console. En la redirigeant, nous pouvons envoyer la sortie dans un fichier à la place. Cela est pratique lorsque vous voulez enregistrer la sortie qui serait autrement simplement affichée sur la console.

Tout d'abord, nous devons créer un nouveau fichier pour écrire le code de notre gestionnaire de contexte. Nous nommerons ce fichier redirect.py. Vous pouvez le créer en utilisant la commande suivante dans le terminal :

touch /home/labex/project/redirect.py

Maintenant que le fichier est créé, ouvrez-le dans un éditeur. Une fois qu'il est ouvert, ajoutez le code Python suivant au fichier :

import sys

class redirect_stdout:
    def __init__(self, out_file):
        self.out_file = out_file

    def __enter__(self):
        self.stdout = sys.stdout
        sys.stdout = self.out_file
        return self.out_file

    def __exit__(self, ty, val, tb):
        sys.stdout = self.stdout

Analysons ce que fait ce gestionnaire de contexte :

  1. __init__ : C'est la méthode d'initialisation. Lorsque nous créons une instance de la classe redirect_stdout, nous passons un objet fichier. Cette méthode stocke cet objet fichier dans la variable d'instance self.out_file. Ainsi, elle se souvient où nous voulons rediriger la sortie.
  2. __enter__ :
    • Tout d'abord, elle sauvegarde le sys.stdout actuel. Cela est important car nous devons le restaurer plus tard.
    • Ensuite, elle remplace le sys.stdout actuel par notre objet fichier. À partir de ce moment, toute sortie qui irait normalement à la console ira dans le fichier à la place.
    • Enfin, elle retourne l'objet fichier. Cela est utile car nous pourrions vouloir utiliser l'objet fichier à l'intérieur du bloc with.
  3. __exit__ :
    • Cette méthode restaure le sys.stdout original. Ainsi, après que le bloc with est terminé, la sortie reviendra normalement à la console.
    • Elle prend trois paramètres : le type d'exception (ty), la valeur de l'exception (val) et la trace d'exécution (tb). Ces paramètres sont requis par le protocole du gestionnaire de contexte. Ils sont utilisés pour gérer les exceptions qui pourraient se produire à l'intérieur du bloc with.

Maintenant, testons notre gestionnaire de contexte. Nous l'utiliserons pour rediriger la sortie d'un tableau dans un fichier. Tout d'abord, lancez l'interpréteur Python :

python3

Ensuite, exécutez le code Python suivant dans l'interpréteur :

>>> import stock, reader, tableformat
>>> from redirect import redirect_stdout
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> formatter = tableformat.create_formatter('text')
>>> with redirect_stdout(open('out.txt', 'w')) as file:
...     tableformat.print_table(portfolio, ['name','shares','price'], formatter)
...     file.close()
...
>>> ## Let's check the content of the output file
>>> print(open('out.txt').read())
      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

Parfait ! Notre gestionnaire de contexte a fonctionné comme prévu. Il a redirigé avec succès la sortie du tableau dans le fichier out.txt.

Les gestionnaires de contexte sont une fonctionnalité très puissante en Python. Ils vous aident à gérer correctement les ressources. Voici quelques cas d'utilisation courants pour les gestionnaires de contexte :

  • Opérations sur les fichiers : Lorsque vous ouvrez un fichier, un gestionnaire de contexte peut s'assurer que le fichier est correctement fermé, même si une erreur se produit.
  • Connexions à la base de données : Il peut garantir que la connexion à la base de données est fermée une fois que vous avez fini de l'utiliser.
  • Verrous dans les programmes multithreadés : Les gestionnaires de contexte peuvent gérer le verrouillage et le déverrouillage des ressources de manière sûre.
  • Changement temporaire des paramètres de l'environnement : Vous pouvez changer certains paramètres pour un bloc de code puis les restaurer automatiquement.

Ce modèle est très important car il garantit que les ressources sont correctement nettoyées, même si une exception se produit à l'intérieur du bloc with.

Une fois que vous avez terminé les tests, vous pouvez quitter l'interpréteur Python :

>>> exit()

Résumé

Dans ce laboratoire (lab), vous avez appris à personnaliser la représentation sous forme de chaîne de caractères des objets en utilisant la méthode __repr__, à rendre les objets comparables avec la méthode __eq__ et à créer un gestionnaire de contexte en utilisant les méthodes __enter__ et __exit__. Ces méthodes spéciales "dunder" sont la pierre angulaire des fonctionnalités orientées objet de Python.

L'implémentation de ces méthodes dans vos classes permet à vos objets de se comporter comme des types intégrés (built - in types) et de s'intégrer parfaitement aux fonctionnalités du langage Python. Les méthodes spéciales permettent diverses fonctionnalités telles que les représentations de chaînes personnalisées, la comparaison d'objets et la gestion de contexte. Au fur et à mesure que vous progresserez en Python, vous découvrirez plus de méthodes spéciales pour exploiter son modèle d'objet puissant.