Fonctions de haut niveau en Python

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 à connaître les fonctions de haut niveau (higher-order functions) en Python. Les fonctions de haut niveau peuvent accepter d'autres fonctions comme arguments ou renvoyer des fonctions en résultat. Ce concept est essentiel en programmation fonctionnelle et vous permet d'écrire un code plus modulaire et réutilisable.

Vous comprendrez ce que sont les fonctions de haut niveau, créerez une fonction qui prend une autre fonction comme argument, refactoriserez des fonctions existantes pour utiliser une fonction de haut niveau et utiliserez la fonction intégrée map() de Python. Le fichier reader.py sera modifié pendant le laboratoire.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python(("Python")) -.-> python/PythonStandardLibraryGroup(["Python Standard Library"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/FunctionsGroup -.-> python/lambda_functions("Lambda Functions") python/FunctionsGroup -.-> python/build_in_functions("Build-in Functions") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/AdvancedTopicsGroup -.-> python/iterators("Iterators") python/PythonStandardLibraryGroup -.-> python/data_collections("Data Collections") subgraph Lab Skills python/function_definition -.-> lab-132505{{"Fonctions de haut niveau en Python"}} python/arguments_return -.-> lab-132505{{"Fonctions de haut niveau en Python"}} python/lambda_functions -.-> lab-132505{{"Fonctions de haut niveau en Python"}} python/build_in_functions -.-> lab-132505{{"Fonctions de haut niveau en Python"}} python/classes_objects -.-> lab-132505{{"Fonctions de haut niveau en Python"}} python/iterators -.-> lab-132505{{"Fonctions de haut niveau en Python"}} python/data_collections -.-> lab-132505{{"Fonctions de haut niveau en Python"}} end

Comprendre la duplication de code

Commençons par examiner le code actuel dans le fichier reader.py. En programmation, examiner le code existant est une étape importante pour comprendre le fonctionnement des choses et identifier les domaines d'amélioration. Vous pouvez ouvrir le fichier reader.py dans l'éditeur WebIDE. Il existe deux façons de le faire. Vous pouvez cliquer sur le fichier dans l'explorateur de fichiers, ou vous pouvez exécuter les commandes suivantes dans le terminal. Ces commandes permettent d'abord de naviguer jusqu'au répertoire du projet, puis d'afficher le contenu du fichier reader.py.

cd ~/project
cat reader.py

Lorsque vous examinez le code, vous remarquerez qu'il y a deux fonctions. Les fonctions en Python sont des blocs de code qui effectuent une tâche spécifique. Voici les deux fonctions et ce qu'elles font :

  1. csv_as_dicts() : Cette fonction prend des données CSV et les convertit en une liste de dictionnaires. Un dictionnaire en Python est une collection de paires clé - valeur, qui est utile pour stocker des données de manière structurée.
  2. csv_as_instances() : Cette fonction prend des données CSV et les convertit en une liste d'instances. Une instance est un objet créé à partir d'une classe, qui est un modèle pour créer des objets.

Maintenant, examinons de plus près ces deux fonctions. Vous verrez qu'elles sont assez similaires. Les deux fonctions suivent les étapes suivantes :

  • Tout d'abord, elles initialisent une liste records vide. Une liste en Python est une collection d'éléments qui peuvent être de différents types. Initialiser une liste vide signifie créer une liste sans éléments, qui sera utilisée pour stocker les données traitées.
  • Ensuite, elles utilisent csv.reader() pour analyser (parser) l'entrée. Analyser signifie examiner les données d'entrée pour extraire des informations significatives. Dans ce cas, csv.reader() nous aide à lire les données CSV ligne par ligne.
  • Elles gèrent les en - têtes de la même manière. Les en - têtes dans un fichier CSV sont la première ligne qui contient généralement les noms des colonnes.
  • Après cela, elles parcourent chaque ligne des données CSV. Une boucle est une structure de programmation qui permet d'exécuter un bloc de code plusieurs fois.
  • Pour chaque ligne, elles la traitent pour créer un enregistrement. Cet enregistrement peut être soit un dictionnaire, soit une instance, selon la fonction.
  • Elles ajoutent (append) l'enregistrement à la liste records. Ajouter signifie ajouter un élément à la fin de la liste.
  • Enfin, elles renvoient la liste records, qui contient toutes les données traitées.

Cette duplication de code pose problème pour plusieurs raisons. Lorsque le code est dupliqué :

  • Il devient plus difficile à maintenir. Si vous avez besoin de modifier le code, vous devez apporter la même modification à plusieurs endroits. Cela prend plus de temps et d'efforts.
  • Toutes les modifications doivent être implémentées à plusieurs endroits. Cela augmente la probabilité que vous oubliiez de faire la modification à l'un des endroits, entraînant un comportement incohérent.
  • Cela augmente également la probabilité d'introduire des bugs. Les bugs sont des erreurs dans le code qui peuvent le faire se comporter de manière inattendue.

La seule différence réelle entre ces deux fonctions est la manière dont elles convertissent une ligne en enregistrement. C'est une situation classique où une fonction de haut niveau (higher - order function) peut être très utile. Une fonction de haut niveau est une fonction qui peut prendre une autre fonction comme argument ou renvoyer une fonction en résultat.

Examinons quelques exemples d'utilisation de ces fonctions pour mieux comprendre leur fonctionnement. Le code suivant montre comment utiliser csv_as_dicts() et csv_as_instances() :

## Example of using csv_as_dicts
with open('portfolio.csv') as f:
    portfolio = csv_as_dicts(f, [str, int, float])
print(portfolio[0])  ## {'name': 'AA', 'shares': 100, 'price': 32.2}

## Example of using csv_as_instances
class Stock:
    @classmethod
    def from_row(cls, row):
        return cls(row[0], int(row[1]), float(row[2]))

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

with open('portfolio.csv') as f:
    portfolio = csv_as_instances(f, Stock)
print(portfolio[0].name, portfolio[0].shares, portfolio[0].price)  ## AA 100 32.2

Dans l'étape suivante, nous allons créer une fonction de haut niveau pour éliminer cette duplication de code. Cela rendra le code plus facilement maintenable et moins sujet aux erreurs.

✨ Vérifier la solution et pratiquer

Création d'une fonction de haut niveau (Higher-Order Function)

En Python, une fonction de haut niveau est une fonction qui peut prendre une autre fonction comme argument. Cela permet une plus grande flexibilité et une meilleure réutilisation du code. Maintenant, créons une fonction de haut niveau appelée convert_csv(). Cette fonction gérera les opérations communes de traitement des données CSV, tout en vous permettant de personnaliser la manière dont chaque ligne du CSV est convertie en un enregistrement.

Ouvrez le fichier reader.py dans l'éditeur WebIDE. Nous allons ajouter une fonction qui prendra un itérable de données CSV, une fonction de conversion et, éventuellement, des en - têtes de colonnes. La fonction de conversion sera utilisée pour transformer chaque ligne du CSV en un enregistrement.

Voici le code de la fonction convert_csv(). Copiez - le et collez - le dans votre fichier reader.py :

def convert_csv(lines, conversion_func, *, headers=None):
    '''
    Convert lines of CSV data using the provided conversion function

    Args:
        lines: An iterable containing CSV data
        conversion_func: A function that takes headers and a row and returns a record
        headers: Column headers (optional). If None, the first row is used as headers

    Returns:
        A list of records as processed by conversion_func
    '''
    records = []
    rows = csv.reader(lines)
    if headers is None:
        headers = next(rows)
    for row in rows:
        record = conversion_func(headers, row)
        records.append(record)
    return records

Analysons ce que fait cette fonction. Tout d'abord, elle initialise une liste vide appelée records pour stocker les enregistrements convertis. Ensuite, elle utilise la fonction csv.reader() pour lire les lignes de données CSV. Si aucun en - tête n'est fourni, elle prend la première ligne comme en - tête. Pour chaque ligne suivante, elle applique la conversion_func pour convertir la ligne en un enregistrement et l'ajoute à la liste records. Enfin, elle renvoie la liste d'enregistrements.

Maintenant, nous avons besoin d'une simple fonction de conversion pour tester notre fonction convert_csv(). Cette fonction prendra les en - têtes et une ligne et convertira la ligne en un dictionnaire en utilisant les en - têtes comme clés.

Voici le code de la fonction make_dict(). Ajoutez également cette fonction à votre fichier reader.py :

def make_dict(headers, row):
    '''
    Convert a row to a dictionary using the provided headers
    '''
    return dict(zip(headers, row))

La fonction make_dict() utilise la fonction zip() pour associer chaque en - tête à sa valeur correspondante dans la ligne, puis crée un dictionnaire à partir de ces paires.

Testons ces fonctions. Ouvrez un interpréteur Python en exécutant les commandes suivantes dans le terminal :

cd ~/project
python3 -i reader.py

L'option -i dans la commande python3 lance l'interpréteur Python en mode interactif et importe le fichier reader.py, afin que nous puissions utiliser les fonctions que nous venons de définir.

Dans l'interpréteur Python, exécutez le code suivant pour tester nos fonctions :

## Open the CSV file
lines = open('portfolio.csv')

## Convert to a list of dictionaries using our new function
result = convert_csv(lines, make_dict)

## Print the result
print(result)

Ce code ouvre le fichier portfolio.csv, utilise la fonction convert_csv() avec la fonction de conversion make_dict() pour convertir les données CSV en une liste de dictionnaires, puis affiche le résultat.

Vous devriez voir une sortie similaire à ceci :

[{'name': 'AA', 'shares': '100', 'price': '32.20'}, {'name': 'IBM', 'shares': '50', 'price': '91.10'}, {'name': 'CAT', 'shares': '150', 'price': '83.44'}, {'name': 'MSFT', 'shares': '200', 'price': '51.23'}, {'name': 'GE', 'shares': '95', 'price': '40.37'}, {'name': 'MSFT', 'shares': '50', 'price': '65.10'}, {'name': 'IBM', 'shares': '100', 'price': '70.44'}]

Cette sortie montre que notre fonction de haut niveau convert_csv() fonctionne correctement. Nous avons créé avec succès une fonction qui prend une autre fonction comme argument, ce qui nous permet de changer facilement la manière dont les données CSV sont converties.

Pour quitter l'interpréteur Python, vous pouvez taper exit() ou appuyer sur Ctrl + D.

✨ Vérifier la solution et pratiquer

Refactorisation des fonctions existantes

Maintenant, nous avons créé une fonction de haut niveau appelée convert_csv(). Les fonctions de haut niveau sont des fonctions qui peuvent prendre d'autres fonctions comme arguments ou renvoyer des fonctions en résultat. Ce sont des concepts puissants en Python qui nous aident à écrire un code plus modulaire et réutilisable. Dans cette section, nous allons utiliser cette fonction de haut niveau pour refactoriser les fonctions originales csv_as_dicts() et csv_as_instances(). La refactorisation est le processus de restructuration d'un code existant sans changer son comportement externe, dans le but d'améliorer sa structure interne, par exemple en éliminant la duplication de code.

Commençons par ouvrir le fichier reader.py dans l'éditeur WebIDE. Nous allons mettre à jour les fonctions comme suit :

  1. Tout d'abord, nous allons remplacer la fonction csv_as_dicts(). Cette fonction est utilisée pour convertir des lignes de données CSV en une liste de dictionnaires. Voici le nouveau code :
def csv_as_dicts(lines, types, *, headers=None):
    '''
    Convert lines of CSV data into a list of dictionaries
    '''
    def dict_converter(headers, row):
        return {name: func(val) for name, func, val in zip(headers, types, row)}

    return convert_csv(lines, dict_converter, headers=headers)

Dans ce code, nous définissons une fonction interne dict_converter qui prend headers et row comme arguments. Elle utilise une compréhension de dictionnaire pour créer un dictionnaire où les clés sont les noms des en - têtes et les valeurs sont le résultat de l'application de la fonction de conversion de type correspondante aux valeurs de la ligne. Ensuite, nous appelons la fonction convert_csv() avec la fonction dict_converter comme argument.

  1. Ensuite, nous allons remplacer la fonction csv_as_instances(). Cette fonction est utilisée pour convertir des lignes de données CSV en une liste d'instances d'une classe donnée. Voici le nouveau code :
def csv_as_instances(lines, cls, *, headers=None):
    '''
    Convert lines of CSV data into a list of instances
    '''
    def instance_converter(headers, row):
        return cls.from_row(row)

    return convert_csv(lines, instance_converter, headers=headers)

Dans ce code, nous définissons une fonction interne instance_converter qui prend headers et row comme arguments. Elle appelle la méthode de classe from_row de la classe donnée cls pour créer une instance à partir de la ligne. Ensuite, nous appelons la fonction convert_csv() avec la fonction instance_converter comme argument.

Après avoir refactorisé ces fonctions, nous devons les tester pour nous assurer qu'elles fonctionnent toujours comme prévu. Pour ce faire, nous allons exécuter les commandes suivantes dans un interpréteur Python :

cd ~/project
python3 -i reader.py

La commande cd ~/project change le répertoire de travail actuel pour le répertoire project. La commande python3 -i reader.py exécute le fichier reader.py en mode interactif, ce qui signifie que nous pouvons continuer à exécuter du code Python après que le fichier ait terminé son exécution.

Une fois l'interpréteur Python ouvert, nous allons exécuter le code suivant pour tester les fonctions refactorisées :

## Define a simple Stock class for testing
class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    @classmethod
    def from_row(cls, row):
        return cls(row[0], int(row[1]), float(row[2]))

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

## Test csv_as_dicts
with open('portfolio.csv') as f:
    portfolio_dicts = csv_as_dicts(f, [str, int, float])
print("First dictionary:", portfolio_dicts[0])

## Test csv_as_instances
with open('portfolio.csv') as f:
    portfolio_instances = csv_as_instances(f, Stock)
print("First instance:", portfolio_instances[0])

Dans ce code, nous définissons tout d'abord une simple classe Stock pour les tests. La méthode __init__ initialise les attributs d'une instance Stock. La méthode de classe from_row crée une instance Stock à partir d'une ligne de données CSV. La méthode __repr__ fournit une représentation sous forme de chaîne de caractères de l'instance Stock.

Ensuite, nous testons la fonction csv_as_dicts() en ouvrant le fichier portfolio.csv et en le passant à la fonction avec une liste de fonctions de conversion de type. Nous affichons le premier dictionnaire de la liste résultante.

Enfin, nous testons la fonction csv_as_instances() en ouvrant le fichier portfolio.csv et en le passant à la fonction avec la classe Stock. Nous affichons la première instance de la liste résultante.

Si tout fonctionne correctement, vous devriez voir une sortie similaire à ceci :

First dictionary: {'name': 'AA', 'shares': 100, 'price': 32.2}
First instance: Stock(AA, 100, 32.2)

Cette sortie indique que nos fonctions refactorisées fonctionnent correctement. Nous avons éliminé avec succès la duplication de code tout en conservant la même fonctionnalité.

Pour quitter l'interpréteur Python, vous pouvez taper exit() ou appuyer sur Ctrl + D.

✨ Vérifier la solution et pratiquer

Utilisation de la fonction map()

En Python, une fonction de haut niveau (Higher-Order Function) est une fonction qui peut prendre une autre fonction comme argument ou renvoyer une fonction en résultat. La fonction map() de Python est un excellent exemple de fonction de haut niveau. C'est un outil puissant qui vous permet d'appliquer une fonction donnée à chaque élément d'un itérable, comme une liste ou un tuple. Après avoir appliqué la fonction à chaque élément, elle renvoie un itérateur des résultats. Cette fonctionnalité rend map() parfait pour le traitement de séquences de données, comme les lignes d'un fichier CSV.

La syntaxe de base de la fonction map() est la suivante :

map(function, iterable, ...)

Ici, la function est l'opération que vous souhaitez effectuer sur chaque élément de l'iterable. L'iterable est une séquence d'éléments, comme une liste ou un tuple.

Regardons un exemple simple. Supposons que vous ayez une liste de nombres et que vous souhaitiez élever chaque nombre de cette liste au carré. Vous pouvez utiliser la fonction map() pour y parvenir. Voici comment vous pouvez le faire :

numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x * x, numbers))
print(squared)  ## Output: [1, 4, 9, 16, 25]

Dans cet exemple, nous définissons d'abord une liste appelée numbers. Ensuite, nous utilisons la fonction map(). La fonction lambda lambda x: x * x est l'opération que nous souhaitons effectuer sur chaque élément de la liste numbers. La fonction map() applique cette fonction lambda à chaque nombre de la liste. Comme map() renvoie un itérateur, nous le convertissons en une liste à l'aide de la fonction list(). Enfin, nous affichons la liste squared, qui contient les valeurs au carré des nombres originaux.

Maintenant, regardons comment nous pouvons utiliser la fonction map() pour modifier notre fonction convert_csv(). Précédemment, nous avons utilisé une boucle for pour itérer sur les lignes des données CSV. Maintenant, nous allons remplacer cette boucle for par la fonction map().

def convert_csv(lines, conversion_func, *, headers=None):
    '''
    Convert lines of CSV data using the provided conversion function
    '''
    rows = csv.reader(lines)
    if headers is None:
        headers = next(rows)

    ## Use map to apply conversion_func to each row
    records = list(map(lambda row: conversion_func(headers, row), rows))
    return records

Cette version mise à jour de la fonction convert_csv() fait exactement la même chose que la version précédente, mais elle utilise la fonction map() au lieu d'une boucle for. La fonction lambda à l'intérieur de map() prend chaque ligne des données CSV et applique la conversion_func à elle, ainsi qu'aux en-têtes.

Testons cette fonction mise à jour pour nous assurer qu'elle fonctionne correctement. Tout d'abord, ouvrez votre terminal et accédez au répertoire du projet. Ensuite, lancez l'interpréteur Python interactif avec le fichier reader.py.

cd ~/project
python3 -i reader.py

Une fois que vous êtes dans l'interpréteur Python, exécutez le code suivant pour tester la fonction convert_csv() mise à jour :

## Test the updated convert_csv function
with open('portfolio.csv') as f:
    result = convert_csv(f, make_dict)
print(result[0])  ## Should print the first dictionary

## Test that csv_as_dicts still works
with open('portfolio.csv') as f:
    portfolio = csv_as_dicts(f, [str, int, float])
print(portfolio[0])  ## Should print the first dictionary with converted types

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

{'name': 'AA', 'shares': '100', 'price': '32.20'}
{'name': 'AA', 'shares': 100, 'price': 32.2}

Cette sortie montre que la fonction convert_csv() mise à jour utilisant la fonction map() fonctionne correctement, et que les fonctions qui en dépendent continuent également de fonctionner comme prévu.

L'utilisation de la fonction map() présente plusieurs avantages :

  1. Elle peut être plus concise qu'une boucle for. Au lieu d'écrire plusieurs lignes de code pour une boucle for, vous pouvez obtenir le même résultat en une seule ligne en utilisant map().
  2. Elle communique clairement votre intention de transformer chaque élément d'une séquence. Lorsque vous voyez map(), vous savez immédiatement que vous appliquez une fonction à chaque élément d'un itérable.
  3. Elle peut être plus économe en mémoire car elle renvoie un itérateur. Un itérateur génère les valeurs à la volée, ce qui signifie qu'il ne stocke pas tous les résultats en mémoire à la fois. Dans notre exemple, nous avons converti l'itérateur renvoyé par map() en une liste, mais dans certains cas, vous pouvez travailler directement avec l'itérateur pour économiser de la mémoire.

Pour quitter l'interpréteur Python, vous pouvez taper exit() ou appuyer sur Ctrl + D.

✨ Vérifier la solution et pratiquer

Résumé

Dans ce laboratoire (lab), vous avez appris à propos des fonctions de haut niveau (Higher-Order Functions) en Python et en quoi elles contribuent à l'écriture d'un code plus modulaire et maintenable. Tout d'abord, vous avez identifié la duplication de code dans deux fonctions similaires. Ensuite, vous avez créé une fonction de haut niveau convert_csv() qui accepte une fonction de conversion comme argument et vous avez refactorisé les fonctions originales pour l'utiliser. Enfin, vous avez mis à jour la fonction de haut niveau pour utiliser la fonction intégrée map() de Python.

Ces techniques sont des atouts puissants dans la boîte à outils d'un programmeur Python. Les fonctions de haut niveau favorisent la réutilisation du code et la séparation des préoccupations, tandis que le passage de fonctions en tant qu'arguments permet un comportement plus flexible et personnalisable. Des fonctions comme map() offrent des moyens concis de transformer des données. Maîtriser ces concepts vous permet d'écrire un code Python plus concis, maintenable et moins sujet aux erreurs.