Vérification de types et interfaces

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 apprendrez à approfondir votre compréhension de la vérification de types et des interfaces en Python. En étendant un module de formatage de tableaux, vous implémenterez des concepts tels que les classes de base abstraites et la validation d'interfaces pour créer un code plus robuste et maintenable.

Ce laboratoire s'appuie sur les concepts des exercices précédents, en mettant l'accent sur la sécurité des types et les modèles de conception d'interfaces. Vos objectifs incluent la mise en œuvre de la vérification de types pour les paramètres de fonction, la création et l'utilisation d'interfaces avec des classes de base abstraites, et l'application du modèle de méthode de gabarit pour réduire la duplication de code. Vous allez modifier tableformat.py, un module pour formater des données sous forme de tableaux, et reader.py, un module pour lire des fichiers CSV.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/ErrorandExceptionHandlingGroup(["Error and Exception Handling"]) python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/inheritance("Inheritance") python/ErrorandExceptionHandlingGroup -.-> python/catching_exceptions("Catching Exceptions") python/ErrorandExceptionHandlingGroup -.-> python/raising_exceptions("Raising Exceptions") subgraph Lab Skills python/conditional_statements -.-> lab-132497{{"Vérification de types et interfaces"}} python/function_definition -.-> lab-132497{{"Vérification de types et interfaces"}} python/classes_objects -.-> lab-132497{{"Vérification de types et interfaces"}} python/inheritance -.-> lab-132497{{"Vérification de types et interfaces"}} python/catching_exceptions -.-> lab-132497{{"Vérification de types et interfaces"}} python/raising_exceptions -.-> lab-132497{{"Vérification de types et interfaces"}} end

Dans cette étape, nous allons améliorer la fonction print_table() dans le fichier tableformat.py. Nous allons ajouter une vérification pour voir si le paramètre formatter est une instance valide de TableFormatter. Pourquoi avons - nous besoin de cela ? Eh bien, la vérification de types est comme un filet de sécurité pour votre code. Elle permet de s'assurer que les données avec lesquelles vous travaillez sont du bon type, ce qui peut éviter de nombreux bugs difficiles à trouver.

Comprendre la vérification de types en Python

La vérification de types est une technique très utile en programmation. Elle vous permet de détecter les erreurs dès le début du processus de développement. En Python, nous avons souvent à faire avec différents types d'objets, et parfois nous attendons qu'un certain type d'objet soit passé à une fonction. Pour vérifier si un objet est d'un type spécifique ou d'une sous - classe de ce type, nous pouvons utiliser la fonction isinstance(). Par exemple, si vous avez une fonction qui attend une liste, vous pouvez utiliser isinstance() pour vous assurer que l'entrée est bien une liste.

Tout d'abord, ouvrez le fichier tableformat.py dans votre éditeur de code. Faites défiler jusqu'au bas du fichier, et vous trouverez la fonction print_table(). Voici à quoi elle ressemble initialement :

def print_table(data, columns, formatter):
    '''
    Print a table showing selected columns from a data source
    using the given formatter.
    '''
    formatter.headings(columns)
    for item in data:
        rowdata = [str(getattr(item, col)) for col in columns]
        formatter.row(rowdata)

Cette fonction prend des données, une liste de colonnes et un formateur. Elle utilise ensuite le formateur pour afficher un tableau. Mais pour l'instant, elle ne vérifie pas si le formateur est du bon type.

Modifions - la pour ajouter la vérification de type. Nous allons utiliser la fonction isinstance() pour vérifier si le paramètre formatter est une instance de TableFormatter. Si ce n'est pas le cas, nous leverons une TypeError avec un message clair. Voici le code modifié :

def print_table(data, columns, formatter):
    '''
    Print a table showing selected columns from a data source
    using the given formatter.
    '''
    if not isinstance(formatter, TableFormatter):
        raise TypeError("Expected a TableFormatter")

    formatter.headings(columns)
    for item in data:
        rowdata = [str(getattr(item, col)) for col in columns]
        formatter.row(rowdata)

Test de votre implémentation de la vérification de types

Maintenant que nous avons ajouté la vérification de type, nous devons nous assurer qu'elle fonctionne. Créons un nouveau fichier Python appelé test_tableformat.py. Voici le code que vous devriez y mettre :

import stock
import reader
import tableformat

## Read portfolio data
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)

## Define a formatter that doesn't inherit from TableFormatter
class MyFormatter:
    def headings(self, headers):
        pass
    def row(self, rowdata):
        pass

## Try to use the non-compliant formatter
try:
    tableformat.print_table(portfolio, ['name', 'shares', 'price'], MyFormatter())
    print("Test failed - type checking not implemented")
except TypeError as e:
    print(f"Test passed - caught error: {e}")

Dans ce code, nous lisons d'abord des données de portefeuille. Ensuite, nous définissons une nouvelle classe de formateur appelée MyFormatter qui n'hérite pas de TableFormatter. Nous essayons d'utiliser ce formateur non conforme dans la fonction print_table(). Si notre vérification de type fonctionne, elle devrait lever une TypeError.

Pour exécuter le test, ouvrez votre terminal et naviguez jusqu'au répertoire où se trouve le fichier test_tableformat.py. Ensuite, exécutez la commande suivante :

python test_tableformat.py

Si tout fonctionne correctement, vous devriez voir une sortie comme celle - ci :

Test passed - caught error: Expected a TableFormatter

Cette sortie confirme que notre vérification de type fonctionne comme prévu. Maintenant, la fonction print_table() n'acceptera que des formateurs qui sont des instances de TableFormatter ou de l'une de ses sous - classes.

✨ Vérifier la solution et pratiquer

Implémentation d'une classe de base abstraite

Dans cette étape, nous allons convertir la classe TableFormatter en une véritable classe de base abstraite (ABC - Abstract Base Class) en utilisant le module abc de Python. Mais d'abord, comprenons ce qu'est une classe de base abstraite et pourquoi nous en avons besoin.

Comprendre les classes de base abstraites

Une classe de base abstraite est un type spécial de classe en Python. C'est une classe à partir de laquelle vous ne pouvez pas créer directement un objet, ce qui signifie que vous ne pouvez pas l'instancier. Le but principal d'une classe de base abstraite est de définir une interface commune pour ses sous - classes. Elle établit un ensemble de règles que toutes les sous - classes doivent suivre. Plus précisément, elle exige que les sous - classes implémentent certaines méthodes.

Voici quelques concepts clés concernant les classes de base abstraites :

  • Nous utilisons le module abc en Python pour créer des classes de base abstraites.
  • Les méthodes marquées avec le décorateur @abstractmethod sont comme des règles. Toute sous - classe qui hérite d'une classe de base abstraite doit implémenter ces méthodes.
  • Si vous essayez de créer un objet d'une classe qui hérite d'une classe de base abstraite mais n'a pas implémenté toutes les méthodes requises, Python lèvera une erreur.

Maintenant que vous comprenez les bases des classes de base abstraites, voyons comment nous pouvons modifier la classe TableFormatter pour en faire une.

Modification de la classe TableFormatter

Ouvrez le fichier tableformat.py. Nous allons apporter quelques modifications à la classe TableFormatter afin qu'elle utilise le module abc et devienne une classe de base abstraite.

  1. Tout d'abord, nous devons importer les éléments nécessaires du module abc. Ajoutez l'instruction d'importation suivante en haut du fichier :
## tableformat.py
from abc import ABC, abstractmethod

Cette instruction d'importation importe deux éléments importants : ABC, qui est une classe de base pour toutes les classes de base abstraites en Python, et abstractmethod, qui est un décorateur que nous utiliserons pour marquer les méthodes comme abstraites.

  1. Ensuite, nous allons modifier la classe TableFormatter. Elle doit hériter de ABC pour devenir une classe de base abstraite, et nous allons marquer ses méthodes comme abstraites en utilisant le décorateur @abstractmethod. Voici à quoi la classe modifiée devrait ressembler :
class TableFormatter(ABC):
    @abstractmethod
    def headings(self, headers):
        '''
        Emit the table headings.
        '''
        pass

    @abstractmethod
    def row(self, rowdata):
        '''
        Emit a single row of table data.
        '''
        pass

Remarquez quelques points concernant cette classe modifiée :

  • La classe hérite maintenant de ABC, ce qui signifie qu'elle est officiellement une classe de base abstraite.
  • Les méthodes headings et row sont toutes deux décorées avec @abstractmethod. Cela indique à Python que toute sous - classe de TableFormatter doit implémenter ces méthodes.
  • Nous avons remplacé NotImplementedError par pass. Le décorateur @abstractmethod s'occupe de s'assurer que les sous - classes implémentent ces méthodes, nous n'avons donc plus besoin de NotImplementedError.

Test de votre classe de base abstraite

Maintenant que nous avons transformé la classe TableFormatter en une classe de base abstraite, testons si elle fonctionne correctement. Nous allons créer un fichier appelé test_abc.py avec le code suivant :

from tableformat import TableFormatter

## Test case 1: Define a class with a misspelled method
try:
    class NewFormatter(TableFormatter):
        def headers(self, headings):  ## Misspelled 'headings'
            pass
        def row(self, rowdata):
            pass

    f = NewFormatter()
    print("Test 1 failed - abstract method enforcement not working")
except TypeError as e:
    print(f"Test 1 passed - caught error: {e}")

## Test case 2: Define a class that properly implements all methods
try:
    class ProperFormatter(TableFormatter):
        def headings(self, headers):
            pass
        def row(self, rowdata):
            pass

    f = ProperFormatter()
    print("Test 2 passed - proper implementation works")
except TypeError as e:
    print(f"Test 2 failed - error: {e}")

Dans ce code, nous avons deux cas de test. Le premier cas de test définit une classe NewFormatter qui tente d'hériter de TableFormatter mais a un nom de méthode mal orthographié. Le deuxième cas de test définit une classe ProperFormatter qui implémente correctement toutes les méthodes requises.

Pour exécuter le test, ouvrez votre terminal et exécutez la commande suivante :

python test_abc.py

Vous devriez voir une sortie similaire à celle - ci :

Test 1 passed - caught error: Can't instantiate abstract class NewFormatter with abstract methods headings
Test 2 passed - proper implementation works

Cette sortie confirme que notre classe de base abstraite fonctionne comme prévu. Le premier cas de test échoue car la classe NewFormatter n'a pas implémenté correctement la méthode headings. Le deuxième cas de test réussit car la classe ProperFormatter a implémenté toutes les méthodes requises.

✨ Vérifier la solution et pratiquer

Création de classes de modèle d'algorithme

Dans cette étape, nous allons utiliser des classes de base abstraites pour implémenter le modèle de méthode de gabarit (template method pattern). L'objectif est de réduire la duplication de code dans la fonctionnalité d'analyse de fichiers CSV. La duplication de code peut rendre votre code plus difficile à maintenir et à mettre à jour. En utilisant le modèle de méthode de gabarit, nous pouvons créer une structure commune pour notre code d'analyse de CSV et laisser les sous - classes gérer les détails spécifiques.

Comprendre le modèle de méthode de gabarit

Le modèle de méthode de gabarit est un modèle de conception comportemental. C'est comme un plan pour un algorithme. Dans une méthode, il définit la structure globale ou le "squelette" d'un algorithme. Cependant, il n'implémente pas entièrement toutes les étapes. Au lieu de cela, il reporte certaines étapes aux sous - classes. Cela signifie que les sous - classes peuvent redéfinir certaines parties de l'algorithme sans changer sa structure globale.

Dans notre cas, si vous regardez le fichier reader.py, vous remarquerez que les fonctions read_csv_as_dicts() et read_csv_as_instances() ont beaucoup de code similaire. La principale différence entre elles est la façon dont elles créent des enregistrements à partir des lignes du fichier CSV. En utilisant le modèle de méthode de gabarit, nous pouvons éviter d'écrire le même code plusieurs fois.

Ajout de la classe de base CSVParser

Commençons par ajouter une classe de base abstraite pour notre analyse de CSV. Ouvrez le fichier reader.py. Nous allons ajouter la classe de base abstraite CSVParser tout en haut du fichier, juste après les instructions d'importation.

## reader.py
import csv
from abc import ABC, abstractmethod

class CSVParser(ABC):
    def parse(self, filename):
        records = []
        with open(filename) as f:
            rows = csv.reader(f)
            headers = next(rows)
            for row in rows:
                record = self.make_record(headers, row)
                records.append(record)
        return records

    @abstractmethod
    def make_record(self, headers, row):
        pass

Cette classe CSVParser sert de modèle pour l'analyse de CSV. La méthode parse contient les étapes communes pour lire un fichier CSV, comme ouvrir le fichier, obtenir les en - têtes et itérer sur les lignes. La logique spécifique pour créer un enregistrement à partir d'une ligne est abstraite dans la méthode make_record(). Étant donné qu'il s'agit d'une méthode abstraite, toute classe qui hérite de CSVParser doit implémenter cette méthode.

Implémentation des classes de parseur concrètes

Maintenant que nous avons notre classe de base, nous devons créer les classes de parseur concrètes. Ces classes implémenteront la logique spécifique de création d'enregistrements.

class DictCSVParser(CSVParser):
    def __init__(self, types):
        self.types = types

    def make_record(self, headers, row):
        return { name: func(val) for name, func, val in zip(headers, self.types, row) }

class InstanceCSVParser(CSVParser):
    def __init__(self, cls):
        self.cls = cls

    def make_record(self, headers, row):
        return self.cls.from_row(row)

La classe DictCSVParser est utilisée pour créer des enregistrements sous forme de dictionnaires. Elle prend une liste de types dans son constructeur. La méthode make_record utilise ces types pour convertir les valeurs de la ligne et créer un dictionnaire.

La classe InstanceCSVParser est utilisée pour créer des enregistrements sous forme d'instances d'une classe. Elle prend une classe dans son constructeur. La méthode make_record appelle la méthode from_row de cette classe pour créer une instance à partir de la ligne.

Refactorisation des fonctions originales

Maintenant, refactorisons les fonctions originales read_csv_as_dicts() et read_csv_as_instances() pour utiliser ces nouvelles classes.

def read_csv_as_dicts(filename, types):
    '''
    Read a CSV file into a list of dictionaries with appropriate type conversion.
    '''
    parser = DictCSVParser(types)
    return parser.parse(filename)

def read_csv_as_instances(filename, cls):
    '''
    Read a CSV file into a list of instances of a class.
    '''
    parser = InstanceCSVParser(cls)
    return parser.parse(filename)

Ces fonctions refactorisées ont la même interface que les originales. Mais en interne, elles utilisent les nouvelles classes de parseur que nous venons de créer. De cette façon, nous avons séparé la logique d'analyse de CSV commune de la logique spécifique de création d'enregistrements.

Test de votre implémentation

Vérifions si notre code refactorisé fonctionne correctement. Créez un fichier nommé test_reader.py et ajoutez le code suivant.

import reader
import stock

## Test the refactored read_csv_as_instances function
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
print("First stock:", portfolio[0])

## Test the refactored read_csv_as_dicts function
portfolio_dicts = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
print("First stock as dict:", portfolio_dicts[0])

## Test direct use of a parser
parser = reader.DictCSVParser([str, int, float])
portfolio_dicts2 = parser.parse('portfolio.csv')
print("First stock from direct parser:", portfolio_dicts2[0])

Pour exécuter le test, ouvrez votre terminal et exécutez la commande suivante :

python test_reader.py

Vous devriez voir une sortie similaire à celle - ci :

First stock: Stock('AA', 100, 32.2)
First stock as dict: {'name': 'AA', 'shares': 100, 'price': 32.2}
First stock from direct parser: {'name': 'AA', 'shares': 100, 'price': 32.2}

Si vous voyez cette sortie, cela signifie que votre code refactorisé fonctionne correctement. Les fonctions originales et l'utilisation directe des parseurs produisent tous les résultats attendus.

✨ Vérifier la solution et pratiquer

Résumé

Dans ce laboratoire, vous avez appris plusieurs concepts clés de la programmation orientée objet pour améliorer le code Python. Tout d'abord, vous avez implémenté la vérification de types dans la fonction print_table(), ce qui garantit que seuls des formateurs valides sont utilisés, améliorant ainsi la robustesse du code. Ensuite, vous avez transformé la classe TableFormatter en une classe de base abstraite, obligeant les sous - classes à implémenter des méthodes spécifiques.

De plus, vous avez appliqué le modèle de méthode de gabarit (template method pattern) en créant la classe de base abstraite CSVParser et ses implémentations concrètes. Cela réduit la duplication de code tout en maintenant une structure d'algorithme cohérente. Ces techniques sont essentielles pour créer un code Python plus maintenable et robuste, en particulier dans les applications à grande échelle. Pour approfondir vos connaissances, explorez les indications de type en Python (PEP 484), les classes de protocole et les modèles de conception en Python.