Introduction
Dans ce laboratoire, vous apprendrez à utiliser l'héritage pour écrire un code extensible et à créer une application pratique qui affiche des données dans plusieurs formats. Vous comprendrez également comment utiliser les classes abstraites de base et leurs implémentations concrètes.
De plus, vous allez implémenter un modèle de fabrique (factory pattern) pour simplifier la sélection des classes. Le fichier que vous allez modifier est tableformat.py.
Comprendre le problème
Dans ce laboratoire, nous allons apprendre l'héritage en Python et comment il peut nous aider à créer un code extensible et adaptable. L'héritage est un concept puissant en programmation orientée objet où une classe peut hériter d'attributs et de méthodes d'une autre classe. Cela nous permet de réutiliser le code et de construire des fonctionnalités plus complexes sur la base du code existant.
Commençons par examiner la fonction existante print_table(). C'est cette fonction que nous allons améliorer pour la rendre plus flexible en termes de formats de sortie.
Tout d'abord, vous devez ouvrir le fichier tableformat.py dans l'éditeur WebIDE. Le chemin de ce fichier est le suivant :
/home/labex/project/tableformat.py
Une fois que vous avez ouvert le fichier, vous verrez l'implémentation actuelle de la fonction print_table(). Cette fonction est conçue pour formater et afficher des données tabulaires. Elle prend deux entrées principales : une liste d'enregistrements (qui sont des objets) et une liste de noms de champs. Sur la base de ces entrées, elle affiche un tableau bien formaté.
Maintenant, testons cette fonction pour voir comment elle fonctionne. Ouvrez un terminal dans le WebIDE et exécutez les commandes Python suivantes. Ces commandes importent les modules nécessaires, lisent les données à partir d'un fichier CSV, puis utilisent la fonction print_table() pour afficher les données.
import stock
import reader
import tableformat
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
tableformat.print_table(portfolio, ['name', 'shares', 'price'])
Après avoir exécuté ces commandes, vous devriez voir la sortie suivante :
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
La sortie est correcte, mais cette fonction présente une limitation. Actuellement, elle ne prend en charge qu'un seul format de sortie, qui est le texte brut. Dans des scénarios réels, vous pourriez vouloir afficher vos données dans différents formats tels que CSV, HTML ou d'autres.
Au lieu de modifier la fonction print_table() chaque fois que nous voulons prendre en charge un nouveau format de sortie, nous pouvons utiliser l'héritage pour créer une solution plus flexible. Voici comment nous allons procéder :
- Nous allons définir une classe de base
TableFormatter. Cette classe aura des méthodes utilisées pour formater les données. La classe de base fournit une structure et une fonctionnalité communes sur lesquelles toutes les sous - classes peuvent s'appuyer. - Nous allons créer diverses sous - classes. Chaque sous - classe sera conçue pour un format de sortie différent. Par exemple, une sous - classe pourrait être pour la sortie au format CSV, une autre pour la sortie au format HTML, etc. Ces sous - classes hériteront des méthodes de la classe de base et peuvent également ajouter leur propre fonctionnalité spécifique.
- Nous allons modifier la fonction
print_table()pour qu'elle puisse fonctionner avec n'importe quel formateur. Cela signifie que nous pouvons passer différentes sous - classes de la classeTableFormatterà la fonctionprint_table(), et elle sera capable d'utiliser les méthodes de formatage appropriées.
Cette approche présente un grand avantage. Elle nous permet d'ajouter de nouveaux formats de sortie sans modifier la fonctionnalité principale de la fonction print_table(). Ainsi, lorsque vos besoins changent et que vous avez besoin de prendre en charge plus de formats de sortie, vous pouvez facilement le faire en créant de nouvelles sous - classes.
Création d'une classe de base et modification de la fonction d'impression
En programmation, l'héritage est un concept puissant qui nous permet de créer une hiérarchie de classes. Pour commencer à utiliser l'héritage pour afficher des données dans différents formats, nous devons d'abord créer une classe de base. Une classe de base sert de modèle pour d'autres classes, en définissant un ensemble commun de méthodes que ses sous - classes peuvent hériter et redéfinir.
Maintenant, créons une classe de base qui définira l'interface pour tous les formateurs de tableaux. Ouvrez le fichier tableformat.py dans le WebIDE et ajoutez le code suivant en haut du fichier :
class TableFormatter:
"""
Base class for all table formatters.
This class defines the interface that all formatters must implement.
"""
def headings(self, headers):
"""
Generate the table headings.
"""
raise NotImplementedError()
def row(self, rowdata):
"""
Generate a single row of table data.
"""
raise NotImplementedError()
La classe TableFormatter est une classe abstraite de base. Une classe abstraite de base est une classe qui définit des méthodes mais ne fournit pas d'implémentations pour elles. Au lieu de cela, elle attend que ses sous - classes fournissent ces implémentations. Les exceptions NotImplementedError sont utilisées pour indiquer que ces méthodes doivent être redéfinies par les sous - classes. Si une sous - classe ne redéfinit pas ces méthodes et que nous essayons de les utiliser, une erreur sera levée.
Ensuite, nous devons modifier la fonction print_table() pour utiliser la classe TableFormatter. La fonction print_table() est utilisée pour afficher un tableau de données à partir d'une liste d'objets. En la modifiant pour utiliser la classe TableFormatter, nous pouvons rendre la fonction plus flexible et capable de fonctionner avec différents formats de tableaux.
Remplacez la fonction print_table() existante par le code suivant :
def print_table(records, fields, formatter):
"""
Print a table of data from a list of objects using the specified formatter.
Args:
records: A list of objects
fields: A list of field names
formatter: A TableFormatter object
"""
formatter.headings(fields)
for r in records:
rowdata = [getattr(r, fieldname) for fieldname in fields]
formatter.row(rowdata)
Le changement clé ici est que print_table() prend maintenant un paramètre formatter, qui doit être une instance de TableFormatter ou d'une sous - classe. Cela signifie que nous pouvons passer différents formateurs de tableaux à la fonction print_table(), et elle utilisera le formateur approprié pour afficher le tableau. La fonction délègue la responsabilité de formatage à l'objet formateur en appelant ses méthodes headings() et row().
Testons nos modifications en essayant d'utiliser la classe de base TableFormatter :
import stock
import reader
import tableformat
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.TableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)
Lorsque vous exécutez ce code, vous devriez voir une erreur :
Traceback (most recent call last):
...
NotImplementedError
Cette erreur se produit parce que nous essayons d'utiliser directement la classe abstraite de base, mais elle ne fournit pas d'implémentations pour ses méthodes. Étant donné que les méthodes headings() et row() dans la classe TableFormatter lèvent NotImplementedError, Python ne sait pas quoi faire lorsque ces méthodes sont appelées. Dans l'étape suivante, nous allons créer une sous - classe concrète qui fournit ces implémentations.
Implémentation d'un formateur concret
Maintenant que nous avons défini notre classe abstraite de base et mis à jour la fonction print_table(), il est temps de créer une classe de formateur concret. Une classe de formateur concret est une classe qui fournit des implémentations réelles pour les méthodes définies dans la classe abstraite de base. Dans notre cas, nous allons créer une classe qui peut formater les données en un tableau texte brut.
Ajoutons la classe suivante à votre fichier tableformat.py. Cette classe héritera de la classe abstraite de base TableFormatter et implémentera les méthodes headings() et row().
class TextTableFormatter(TableFormatter):
"""
Formatter that generates a plain - text table.
"""
def headings(self, headers):
"""
Generate plain - text table headings.
"""
print(' '.join('%10s' % h for h in headers))
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
"""
Generate a plain - text table row.
"""
print(' '.join('%10s' % d for d in rowdata))
La classe TextTableFormatter hérite de TableFormatter. Cela signifie qu'elle obtient toutes les propriétés et méthodes de la classe TableFormatter, mais elle fournit également ses propres implémentations pour les méthodes headings() et row(). Ces méthodes sont respectivement responsables du formatage des en - têtes et des lignes du tableau. La méthode headings() affiche les en - têtes d'une manière bien formatée, suivie d'une ligne de tirets pour séparer les en - têtes des données. La méthode row() formate chaque ligne de données de manière similaire.
Maintenant, testons notre nouveau formateur. Nous allons utiliser les modules stock, reader et tableformat pour lire les données à partir d'un fichier CSV et les afficher en utilisant notre nouveau formateur.
import stock
import reader
import tableformat
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.TextTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)
Lorsque vous exécutez ce code, vous devriez voir la même sortie que précédemment. C'est parce que notre nouveau formateur est conçu pour produire le même tableau texte brut que la fonction print_table() originale.
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
Cette sortie confirme que notre TextTableFormatter fonctionne correctement. L'avantage d'utiliser cette approche est que nous avons rendu notre code plus modulaire et extensible. En séparant la logique de formatage dans une hiérarchie de classes distincte, nous pouvons facilement ajouter de nouveaux formats de sortie. Tout ce que nous avons à faire est de créer de nouvelles sous - classes de TableFormatter sans modifier la fonction print_table(). De cette façon, nous pourrons prendre en charge différents formats de sortie tels que CSV ou HTML à l'avenir.
Création de formateurs supplémentaires
En programmation, l'héritage est un concept puissant qui nous permet de créer de nouvelles classes à partir de classes existantes. Cela facilite la réutilisation du code et rend nos programmes plus extensibles. Dans cette partie de l'expérience, nous allons utiliser l'héritage pour créer deux nouveaux formateurs pour différents formats de sortie : CSV et HTML. Ces formateurs hériteront d'une classe de base, ce qui signifie qu'ils partageront certains comportements communs tout en ayant leurs propres manières uniques de formater les données.
Maintenant, ajoutons les classes suivantes à votre fichier tableformat.py. Ces classes définiront comment formater les données respectivement au format CSV et HTML.
class CSVTableFormatter(TableFormatter):
"""
Formatter that generates CSV formatted data.
"""
def headings(self, headers):
"""
Generate CSV headers.
"""
print(','.join(headers))
def row(self, rowdata):
"""
Generate a CSV data row.
"""
print(','.join(str(d) for d in rowdata))
class HTMLTableFormatter(TableFormatter):
"""
Formatter that generates HTML table code.
"""
def headings(self, headers):
"""
Generate HTML table headers.
"""
print('<tr>', end=' ')
for header in headers:
print(f'<th>{header}</th>', end=' ')
print('</tr>')
def row(self, rowdata):
"""
Generate an HTML table row.
"""
print('<tr>', end=' ')
for data in rowdata:
print(f'<td>{data}</td>', end=' ')
print('</tr>')
La classe CSVTableFormatter est conçue pour formater les données au format CSV (Comma-Separated Values, valeurs séparées par des virgules). La méthode headings prend une liste d'en - têtes et les affiche séparées par des virgules. La méthode row prend une liste de données pour une seule ligne et les affiche également séparées par des virgules.
La classe HTMLTableFormatter, en revanche, est utilisée pour générer du code de tableau HTML. La méthode headings crée les en - têtes de tableau en utilisant les balises HTML <th>, et la méthode row crée une ligne de tableau en utilisant les balises HTML <td>.
Testons ces nouveaux formateurs pour voir comment ils fonctionnent.
- Tout d'abord, testons le formateur CSV :
import stock
import reader
import tableformat
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.CSVTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)
Dans ce code, nous importons d'abord les modules nécessaires. Ensuite, nous lisons les données à partir d'un fichier CSV nommé portfolio.csv et créons des instances de la classe Stock. Ensuite, nous créons une instance de la classe CSVTableFormatter. Enfin, nous utilisons la fonction print_table pour afficher les données du portefeuille au format CSV.
Vous devriez voir la sortie au format CSV suivante :
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, testons le formateur HTML :
formatter = tableformat.HTMLTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)
Ici, nous créons une instance de la classe HTMLTableFormatter et utilisons à nouveau la fonction print_table pour afficher les données du portefeuille au format HTML.
Vous devriez voir la sortie au format HTML suivante :
<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>
<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>
<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>
<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>
<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>
<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>
<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>
<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>
Comme vous pouvez le voir, chaque formateur produit une sortie dans un format différent, mais ils partagent tous la même interface définie par la classe de base TableFormatter. Ceci est un excellent exemple de la puissance de l'héritage et du polymorphisme. Nous pouvons écrire du code qui fonctionne avec la classe de base, et il fonctionnera automatiquement avec n'importe quelle sous - classe.
La fonction print_table() n'a pas besoin de savoir quoi que ce soit sur le formateur spécifique utilisé. Elle appelle simplement les méthodes définies dans la classe de base, et l'implémentation appropriée est sélectionnée en fonction du type de formateur fourni. Cela rend notre code plus flexible et plus facile à maintenir.
Création d'une fonction usine
Lors de l'utilisation de l'héritage, un défi commun est que les utilisateurs doivent se souvenir des noms des classes de formateurs spécifiques. Cela peut être assez ennuyeux, surtout à mesure que le nombre de classes augmente. Pour simplifier ce processus, nous pouvons créer une fonction usine. Une fonction usine est un type spécial de fonction qui crée et retourne des objets. Dans notre cas, elle retournera le formateur approprié en fonction d'un nom de format simple.
Ajoutons la fonction suivante à votre fichier tableformat.py. Cette fonction prendra un nom de format en argument et retournera l'objet formateur correspondant.
def create_formatter(format_name):
"""
Create a formatter of the specified type.
Args:
format_name: Name of the formatter ('text', 'csv', 'html')
Returns:
A TableFormatter object
Raises:
ValueError: If format_name is not recognized
"""
if format_name == 'text':
return TextTableFormatter()
elif format_name == 'csv':
return CSVTableFormatter()
elif format_name == 'html':
return HTMLTableFormatter()
else:
raise ValueError(f'Unknown format {format_name}')
La fonction create_formatter() est une fonction usine. Elle vérifie l'argument format_name que vous fournissez. Si c'est 'text', elle crée et retourne un objet TextTableFormatter. Si c'est 'csv', elle retourne un objet CSVTableFormatter, et si c'est 'html', elle retourne un objet HTMLTableFormatter. Si le nom de format n'est pas reconnu, elle lève une erreur ValueError. De cette façon, les utilisateurs peuvent facilement sélectionner un formateur en fournissant simplement un nom, sans avoir à connaître les noms de classes spécifiques.
Maintenant, testons la fonction usine. Nous allons utiliser certaines fonctions et classes existantes pour lire des données à partir d'un fichier CSV et les afficher dans différents formats.
import stock
import reader
from tableformat import create_formatter, print_table
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
## Test with text formatter
formatter = create_formatter('text')
print("\nText Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)
## Test with CSV formatter
formatter = create_formatter('csv')
print("\nCSV Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)
## Test with HTML formatter
formatter = create_formatter('html')
print("\nHTML Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)
Dans ce code, nous importons d'abord les modules et les fonctions nécessaires. Ensuite, nous lisons les données à partir du fichier portfolio.csv et créons un objet portfolio. Après cela, nous testons la fonction create_formatter() avec différents noms de format : 'text', 'csv' et 'html'. Pour chaque format, nous créons un objet formateur, affichons le nom du format, puis utilisons la fonction print_table() pour afficher les données du portfolio dans le format spécifié.
Lorsque vous exécutez ce code, vous devriez voir des sorties dans les trois formats, séparées par le nom du format :
Text Format:
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
CSV Format:
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
HTML Format:
<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>
<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>
<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>
<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>
<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>
<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>
<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>
<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>
La fonction usine rend le code plus convivial car elle cache les détails de l'instanciation des classes. Les utilisateurs n'ont pas besoin de savoir comment créer des objets formateurs ; ils ont juste besoin de spécifier le format qu'ils souhaitent.
Ce modèle d'utilisation d'une fonction usine pour créer des objets est un modèle de conception commun en programmation orientée objet, connu sous le nom de modèle usine (Factory Pattern). Il fournit une couche d'abstraction entre le code client (le code qui utilise le formateur) et les classes d'implémentation réelles (les classes de formateurs). Cela rend le code plus modulaire et plus facile à utiliser.
Récapitulatif des concepts clés :
Classe abstraite de base : La classe
TableFormattersert d'interface. Une interface définit un ensemble de méthodes que toutes les classes qui l'implémentent doivent avoir. Dans notre cas, toutes les classes de formateurs doivent implémenter les méthodes définies dans la classeTableFormatter.Héritage : Les classes de formateurs concrètes, comme
TextTableFormatter,CSVTableFormatteretHTMLTableFormatter, héritent de la classe de baseTableFormatter. Cela signifie qu'elles obtiennent la structure et les méthodes de base de la classe de base et peuvent fournir leurs propres implémentations spécifiques.Polymorphisme : La fonction
print_table()peut fonctionner avec n'importe quel formateur qui implémente l'interface requise. Cela signifie que vous pouvez passer différents objets formateurs à la fonctionprint_table(), et elle fonctionnera correctement avec chacun d'eux.Modèle usine : La fonction
create_formatter()simplifie la création d'objets formateurs. Elle prend en charge les détails de la création du bon objet en fonction du nom de format, de sorte que les utilisateurs n'ont pas à s'en soucier.
En utilisant ces principes de programmation orientée objet, nous avons créé un système flexible et extensible pour formater des données tabulaires dans différents formats de sortie.
Résumé
Dans ce laboratoire (lab), vous avez appris plusieurs concepts clés en programmation orientée objet. Vous avez maîtrisé l'utilisation de l'héritage pour créer du code extensible, la définition d'une classe abstraite de base comme interface et l'implémentation de sous - classes concrètes qui héritent d'une classe de base. De plus, vous avez appris à utiliser le polymorphisme pour écrire du code qui fonctionne avec différents types et le modèle usine (Factory Pattern) pour simplifier la création d'objets.
Ces concepts sont des outils puissants pour créer du code maintenable et extensible. Le système de formatage de tableaux que vous avez construit montre comment l'héritage peut créer un système flexible. Vous pouvez appliquer ces principes à d'autres tâches de programmation, rendant votre code modulaire, réutilisable et adaptable aux exigences changeantes.