Classes Mixin et Héritage Coopératif

PythonPythonBeginner
Pratiquer maintenant

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

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Dans ce laboratoire (lab), vous allez apprendre les classes mixin et leur rôle dans l'amélioration de la réutilisabilité du code. Vous comprendrez comment implémenter les mixins pour étendre la fonctionnalité des classes sans modifier le code existant.

Vous maîtriserez également les techniques d'héritage coopératif en Python. Le fichier tableformat.py sera modifié au cours de l'expérience.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/default_arguments("Default Arguments") python/FunctionsGroup -.-> python/keyword_arguments("Keyword Arguments") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/inheritance("Inheritance") subgraph Lab Skills python/function_definition -.-> lab-132498{{"Classes Mixin et Héritage Coopératif"}} python/default_arguments -.-> lab-132498{{"Classes Mixin et Héritage Coopératif"}} python/keyword_arguments -.-> lab-132498{{"Classes Mixin et Héritage Coopératif"}} python/classes_objects -.-> lab-132498{{"Classes Mixin et Héritage Coopératif"}} python/inheritance -.-> lab-132498{{"Classes Mixin et Héritage Coopératif"}} end

Comprendre le problème de formatage des colonnes

Dans cette étape, nous allons examiner une limitation de notre implémentation actuelle de formatage de tableaux. Nous allons également étudier quelques solutions possibles à ce problème.

Tout d'abord, comprenons ce que nous allons faire. Nous allons ouvrir l'éditeur VSCode et regarder le fichier tableformat.py dans le répertoire du projet. Ce fichier est important car il contient le code qui nous permet de formater des données tabulaires de différentes manières, comme en texte brut, au format CSV ou au format HTML.

Pour ouvrir le fichier, nous allons utiliser les commandes suivantes dans le terminal. La commande cd change le répertoire pour le répertoire du projet, et la commande code ouvre le fichier tableformat.py dans VSCode.

cd ~/project
code tableformat.py

Lorsque vous ouvrez le fichier, vous remarquerez qu'il y a plusieurs classes définies. Ces classes jouent différents rôles dans le formatage des données du tableau.

  • TableFormatter : Il s'agit d'une classe abstraite de base. Elle a des méthodes qui sont utilisées pour formater les en-têtes et les lignes du tableau. Considérez-la comme un modèle pour les autres classes de formateur.
  • TextTableFormatter : Cette classe est utilisée pour afficher le tableau au format texte brut.
  • CSVTableFormatter : Elle est responsable du formatage des données du tableau au format CSV (Comma-Separated Values, valeurs séparées par des virgules).
  • HTMLTableFormatter : Cette classe formate les données du tableau au format HTML.

Il y a également une fonction print_table() dans le fichier. Cette fonction utilise les classes de formateur que nous venons de mentionner pour afficher les données tabulaires.

Maintenant, voyons comment ces classes fonctionnent en exécutant un peu de code Python. Ouvrez un terminal et démarrez une session Python. Le code suivant importe les fonctions et les classes nécessaires du fichier tableformat.py, crée un objet TextTableFormatter, puis utilise la fonction print_table() pour afficher les données du portefeuille.

python3 -c "
from tableformat import print_table, TextTableFormatter, portfolio
formatter = TextTableFormatter()
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"

Après avoir exécuté le code, vous devriez voir une sortie similaire à ceci :

      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

Maintenant, trouvons le problème. Remarquez que les valeurs dans la colonne price ne sont pas formatées de manière cohérente. Certaines valeurs ont une décimale, comme 32.2, tandis que d'autres en ont deux, comme 51.23. Dans les données financières, nous voulons généralement que le formatage soit cohérent.

Voici à quoi nous aimerions que la sortie ressemble :

      name     shares      price
---------- ---------- ----------
        AA        100      32.20
       IBM         50      91.10
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50      65.10
       IBM        100      70.44

Une façon de résoudre ce problème est de modifier la fonction print_table() pour qu'elle accepte des spécifications de format. Le code suivant montre comment nous pouvons le faire. Nous définissons une nouvelle fonction print_table() qui prend un paramètre supplémentaire formats. À l'intérieur de la fonction, nous utilisons ces spécifications de format pour formater chaque valeur de la ligne.

python3 -c "
from tableformat import TextTableFormatter, portfolio

def print_table(records, fields, formats, formatter):
    formatter.headings(fields)
    for r in records:
        rowdata = [(fmt % getattr(r, fieldname))
             for fieldname, fmt in zip(fields, formats)]
        formatter.row(rowdata)

formatter = TextTableFormatter()
print_table(portfolio,
            ['name','shares','price'],
            ['%s','%d','%0.2f'],
            formatter)
"

Cette solution fonctionne, mais elle a un inconvénient. Changer l'interface de la fonction peut casser le code existant qui utilise l'ancienne version de la fonction print_table().

Une autre approche consiste à créer un formateur personnalisé en utilisant la sous-classification. Nous pouvons créer une nouvelle classe qui hérite de TextTableFormatter et qui remplace la méthode row() pour appliquer le formatage souhaité.

python3 -c "
from tableformat import TextTableFormatter, print_table, portfolio

class PortfolioFormatter(TextTableFormatter):
    def row(self, rowdata):
        formats = ['%s','%d','%0.2f']
        rowdata = [(fmt % d) for fmt, d in zip(formats, rowdata)]
        super().row(rowdata)

formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"

Cette solution fonctionne également, mais elle n'est pas très pratique. Chaque fois que nous voulons un formatage différent, nous devons créer une nouvelle classe. Et nous sommes limités au type de formateur spécifique dont nous dérivons, dans ce cas, TextTableFormatter.

Dans l'étape suivante, nous allons explorer une solution plus élégante en utilisant les classes mixin.

Implémentation de classes mixin pour le formatage

Dans cette étape, nous allons apprendre les classes mixin. Les classes mixin sont une technique très utile en Python. Elles vous permettent d'ajouter des fonctionnalités supplémentaires aux classes sans modifier leur code d'origine. C'est très pratique car cela aide à maintenir votre code modulaire et facile à gérer.

Qu'est-ce que les classes mixin ?

Une classe mixin est un type spécial de classe. Son but principal est de fournir une fonctionnalité qui peut être héritée par une autre classe. Cependant, une classe mixin n'est pas destinée à être utilisée seule. Vous ne créez pas directement une instance d'une classe mixin. Au lieu de cela, vous l'utilisez pour ajouter des fonctionnalités spécifiques à d'autres classes de manière contrôlée et prévisible. C'est une forme d'héritage multiple, où une classe peut hériter de plusieurs classes parentes.

Maintenant, implémentons deux classes mixin dans notre fichier tableformat.py. Tout d'abord, ouvrez le fichier dans l'éditeur. Vous pouvez le faire en exécutant les commandes suivantes dans votre terminal :

cd ~/project
code tableformat.py

Une fois le fichier ouvert, ajoutez les définitions de classe suivantes à la fin du fichier, mais avant toute fonction existante.

class ColumnFormatMixin:
    formats = []
    def row(self, rowdata):
        rowdata = [(fmt % d) for fmt, d in zip(self.formats, rowdata)]
        super().row(rowdata)

Cette classe ColumnFormatMixin fournit une fonctionnalité de formatage des colonnes. La variable de classe formats est une liste qui contient des codes de format. Ces codes sont utilisés pour formater les données de chaque colonne. La méthode row() prend les données de la ligne, applique les codes de format à chaque élément de la ligne, puis transmet les données de la ligne formatée à la classe parente en utilisant super().row(rowdata).

Ensuite, ajoutez une autre classe mixin qui fait apparaître les en-têtes de tableau en majuscules :

class UpperHeadersMixin:
    def headings(self, headers):
        super().headings([h.upper() for h in headers])

Cette classe UpperHeadersMixin transforme le texte des en-têtes en majuscules. Elle prend la liste des en-têtes, convertit chaque en-tête en majuscules, puis transmet les en-têtes modifiés à la méthode headings() de la classe parente en utilisant super().headings().

Utilisation des classes mixin

Testons nos nouvelles classes mixin. Nous allons exécuter un peu de code Python pour voir comment elles fonctionnent.

python3 -c "
from tableformat import TextTableFormatter, ColumnFormatMixin, portfolio, print_table

class PortfolioFormatter(ColumnFormatMixin, TextTableFormatter):
    formats = ['%s', '%d', '%0.2f']

formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"

Lorsque vous exécutez ce code, vous devriez voir une sortie bien formatée. La colonne des prix aura un nombre cohérent de décimales grâce au formatage fourni par la classe ColumnFormatMixin.

      name     shares      price
---------- ---------- ----------
        AA        100      32.20
       IBM         50      91.10
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50      65.10
       IBM        100      70.44

Maintenant, essayons la classe UpperHeadersMixin. Exécutez le code suivant :

python3 -c "
from tableformat import TextTableFormatter, UpperHeadersMixin, portfolio, print_table

class PortfolioFormatter(UpperHeadersMixin, TextTableFormatter):
    pass

formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"

Ce code devrait afficher les en-têtes en majuscules.

      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

Comprendre l'héritage coopératif

Remarquez que dans nos classes mixin, nous utilisons super().method(). Cela s'appelle "héritage coopératif". Dans l'héritage coopératif, chaque classe dans la chaîne d'héritage travaille ensemble. Lorsqu'une classe appelle super().method(), elle demande à la classe suivante dans la chaîne d'exécuter sa partie de la tâche. De cette façon, une chaîne de classes peut ajouter chacune son propre comportement au processus global.

L'ordre d'héritage est très important. Lorsque nous définissons class PortfolioFormatter(ColumnFormatMixin, TextTableFormatter), Python cherche d'abord les méthodes dans ColumnFormatMixin, puis dans TextTableFormatter. Donc, lorsque super().row() est appelé dans ColumnFormatMixin, il fait référence à TextTableFormatter.row().

Nous pouvons même combiner les deux mixins. Exécutez le code suivant :

python3 -c "
from tableformat import TextTableFormatter, ColumnFormatMixin, UpperHeadersMixin, portfolio, print_table

class PortfolioFormatter(ColumnFormatMixin, UpperHeadersMixin, TextTableFormatter):
    formats = ['%s', '%d', '%0.2f']

formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"

Ce code nous donnera à la fois des en-têtes en majuscules et des nombres formatés.

      NAME     SHARES      PRICE
---------- ---------- ----------
        AA        100      32.20
       IBM         50      91.10
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50      65.10
       IBM        100      70.44

Dans l'étape suivante, nous allons rendre ces mixins plus faciles à utiliser en améliorant la fonction create_formatter().

✨ Vérifier la solution et pratiquer

Création d'une API conviviale pour les mixins

Les mixins sont une fonctionnalité puissante en Python, mais elles peuvent être un peu délicates pour les débutants car elles impliquent l'héritage multiple, qui peut devenir assez complexe. Dans cette étape, nous allons faciliter les choses pour les utilisateurs en améliorant la fonction create_formatter(). De cette façon, les utilisateurs n'auront pas à se soucier trop des détails de l'héritage multiple.

Tout d'abord, vous devez ouvrir le fichier tableformat.py. Vous pouvez le faire en exécutant les commandes suivantes dans votre terminal. La commande cd change le répertoire pour le dossier de votre projet, et la commande code ouvre le fichier tableformat.py dans votre éditeur de code.

cd ~/project
code tableformat.py

Une fois le fichier ouvert, recherchez la fonction create_formatter(). Actuellement, elle ressemble à ceci :

def create_formatter(name):
    """
    Create an appropriate formatter based on the name.
    """
    if name == 'text':
        return TextTableFormatter()
    elif name == 'csv':
        return CSVTableFormatter()
    elif name == 'html':
        return HTMLTableFormatter()
    else:
        raise RuntimeError(f'Unknown format {name}')

Cette fonction prend un nom en argument et renvoie le formateur correspondant. Mais nous voulons la rendre plus flexible. Nous allons la modifier pour qu'elle puisse accepter des arguments optionnels pour nos mixins.

Remplacez la fonction create_formatter() existante par la version améliorée ci-dessous. Cette nouvelle fonction vous permet de spécifier les formats de colonne et si les en-têtes doivent être convertis en majuscules.

def create_formatter(name, column_formats=None, upper_headers=False):
    """
    Create a formatter with optional enhancements.

    Parameters:
    name : str
        Name of the formatter ('text', 'csv', 'html')
    column_formats : list, optional
        List of format strings for column formatting
    upper_headers : bool, optional
        Whether to convert headers to uppercase
    """
    if name == 'text':
        formatter_cls = TextTableFormatter
    elif name == 'csv':
        formatter_cls = CSVTableFormatter
    elif name == 'html':
        formatter_cls = HTMLTableFormatter
    else:
        raise RuntimeError(f'Unknown format {name}')

    ## Apply mixins if requested
    if column_formats and upper_headers:
        class CustomFormatter(ColumnFormatMixin, UpperHeadersMixin, formatter_cls):
            formats = column_formats
        return CustomFormatter()
    elif column_formats:
        class CustomFormatter(ColumnFormatMixin, formatter_cls):
            formats = column_formats
        return CustomFormatter()
    elif upper_headers:
        class CustomFormatter(UpperHeadersMixin, formatter_cls):
            pass
        return CustomFormatter()
    else:
        return formatter_cls()

Cette fonction améliorée fonctionne en déterminant d'abord la classe de formateur de base en fonction de l'argument name. Ensuite, selon que column_formats et upper_headers sont fournis, elle crée une classe de formateur personnalisée qui inclut les mixins appropriés. Enfin, elle renvoie une instance de la classe de formateur personnalisée.

Maintenant, testons notre fonction améliorée avec différentes combinaisons d'options.

Tout d'abord, essayons d'utiliser le formatage des colonnes. Exécutez la commande suivante dans votre terminal. Cette commande importe les fonctions et les données nécessaires du fichier tableformat.py, crée un formateur avec le formatage des colonnes, puis affiche un tableau en utilisant ce formateur.

python3 -c "
from tableformat import create_formatter, portfolio, print_table

formatter = create_formatter('text', column_formats=['%s', '%d', '%0.2f'])
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"

Vous devriez voir le tableau avec des colonnes formatées. La sortie ressemblera à ceci :

      name     shares      price
---------- ---------- ----------
        AA        100      32.20
       IBM         50      91.10
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50      65.10
       IBM        100      70.44

Ensuite, essayons d'utiliser des en-têtes en majuscules. Exécutez la commande suivante :

python3 -c "
from tableformat import create_formatter, portfolio, print_table

formatter = create_formatter('text', upper_headers=True)
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"

Vous devriez voir le tableau avec des en-têtes en majuscules. La sortie sera :

      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

Enfin, combinons les deux options. Exécutez cette commande :

python3 -c "
from tableformat import create_formatter, portfolio, print_table

formatter = create_formatter('text', column_formats=['%s', '%d', '%0.2f'], upper_headers=True)
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"

Cela devrait afficher un tableau avec des colonnes formatées et des en-têtes en majuscules. La sortie sera :

      NAME     SHARES      PRICE
---------- ---------- ----------
        AA        100      32.20
       IBM         50      91.10
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50      65.10
       IBM        100      70.44

La fonction améliorée fonctionne également avec d'autres types de formateurs. Par exemple, essayons-la avec le formateur CSV. Exécutez la commande suivante :

python3 -c "
from tableformat import create_formatter, portfolio, print_table

formatter = create_formatter('csv', column_formats=['\\"%s\\"', '%d', '%0.2f'])
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"

Cela devrait produire une sortie CSV avec des colonnes formatées. La sortie sera :

name,shares,price
"AA",100,32.20
"IBM",50,91.10
"CAT",150,83.44
"MSFT",200,51.23
"GE",95,40.37
"MSFT",50,65.10
"IBM",100,70.44

En améliorant la fonction create_formatter(), nous avons créé une API conviviale. Les utilisateurs peuvent maintenant facilement utiliser les mixins sans avoir à comprendre les détails complexes de l'héritage multiple. Cela leur donne la flexibilité de personnaliser les formateurs selon leurs besoins.

✨ Vérifier la solution et pratiquer

Résumé

Dans ce laboratoire, vous avez appris les classes mixin et l'héritage coopératif en Python, qui sont des techniques puissantes pour étendre la fonctionnalité des classes sans modifier le code existant. Vous avez exploré des concepts clés tels que la compréhension des limitations de l'héritage simple, la création de classes mixin pour des fonctionnalités ciblées et l'utilisation de super() pour l'héritage coopératif afin de construire des chaînes de méthodes.

Ces techniques sont précieuses pour écrire un code Python maintenable et extensible, en particulier dans les frameworks et les bibliothèques. Elles vous permettent de fournir des points de personnalisation sans demander aux utilisateurs de réécrire le code existant, et permettent de combiner plusieurs mixins pour composer des comportements complexes tout en masquant la complexité de l'héritage dans des API conviviales.