Retourner des valeurs depuis des fonctions

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 à retourner plusieurs valeurs à partir de fonctions en Python. Vous comprendrez également les valeurs de retour facultatives et comment gérer efficacement les erreurs.

De plus, vous explorerez le concept de Futures pour la programmation concurrente. Bien que le retour d'une valeur puisse sembler simple, différents scénarios de programmation présentent divers modèles et considérations.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/DataStructuresGroup(["Data Structures"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ErrorandExceptionHandlingGroup(["Error and Exception Handling"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python(("Python")) -.-> python/PythonStandardLibraryGroup(["Python Standard Library"]) python/DataStructuresGroup -.-> python/tuples("Tuples") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/ErrorandExceptionHandlingGroup -.-> python/catching_exceptions("Catching Exceptions") python/AdvancedTopicsGroup -.-> python/threading_multiprocessing("Multithreading and Multiprocessing") python/PythonStandardLibraryGroup -.-> python/data_collections("Data Collections") subgraph Lab Skills python/tuples -.-> lab-132504{{"Retourner des valeurs depuis des fonctions"}} python/function_definition -.-> lab-132504{{"Retourner des valeurs depuis des fonctions"}} python/arguments_return -.-> lab-132504{{"Retourner des valeurs depuis des fonctions"}} python/catching_exceptions -.-> lab-132504{{"Retourner des valeurs depuis des fonctions"}} python/threading_multiprocessing -.-> lab-132504{{"Retourner des valeurs depuis des fonctions"}} python/data_collections -.-> lab-132504{{"Retourner des valeurs depuis des fonctions"}} end

Retourner plusieurs valeurs à partir de fonctions

En Python, lorsque vous avez besoin qu'une fonction retourne plus d'une valeur, il existe une solution pratique : retourner un tuple. Un tuple est un type de structure de données en Python. C'est une séquence immuable, ce qui signifie qu'une fois que vous avez créé un tuple, vous ne pouvez pas modifier ses éléments. Les tuples sont utiles car ils peuvent contenir plusieurs valeurs de différents types en un seul endroit.

Créons une fonction pour analyser les lignes de configuration au format nom=valeur. Le but de cette fonction est de prendre une ligne dans ce format et de retourner à la fois le nom et la valeur comme éléments distincts.

  1. Tout d'abord, vous devez créer un nouveau fichier Python. Ce fichier contiendra le code de notre fonction et le code de test. Dans le répertoire du projet, créez un fichier nommé return_values.py. Vous pouvez utiliser la commande suivante dans le terminal pour créer ce fichier :
touch ~/project/return_values.py
  1. Maintenant, ouvrez le fichier return_values.py dans votre éditeur de code. À l'intérieur de ce fichier, nous allons écrire la fonction parse_line. Cette fonction prend une ligne en entrée, la divise au niveau du premier signe '=' et retourne le nom et la valeur sous forme de tuple.
def parse_line(line):
    """
    Parse a line in the format 'name=value' and return both the name and value.

    Args:
        line (str): Input line to parse in the format 'name=value'

    Returns:
        tuple: A tuple containing (name, value)
    """
    parts = line.split('=', 1)  ## Split at the first equals sign
    if len(parts) == 2:
        name = parts[0]
        value = parts[1]
        return (name, value)  ## Return as a tuple

Dans cette fonction, la méthode split est utilisée pour diviser la ligne d'entrée en deux parties au niveau du premier signe '='. Si la ligne est au bon format nom=valeur, nous extrayons le nom et la valeur et les retournons sous forme de tuple.

  1. Après avoir défini la fonction, nous devons ajouter un peu de code de test pour voir si la fonction fonctionne comme prévu. Le code de test appellera la fonction parse_line avec une entrée d'exemple et affichera les résultats.
## Test the parse_line function
if __name__ == "__main__":
    result = parse_line('email=guido@python.org')
    print(f"Result as tuple: {result}")

    ## Unpacking the tuple into separate variables
    name, value = parse_line('email=guido@python.org')
    print(f"Unpacked name: {name}")
    print(f"Unpacked value: {value}")

Dans le code de test, nous appelons d'abord la fonction parse_line et stockons le tuple retourné dans la variable result. Ensuite, nous affichons ce tuple. Ensuite, nous utilisons le déballage de tuple pour assigner directement les éléments du tuple aux variables name et value et les affichons séparément.

  1. Une fois que vous avez écrit la fonction et le code de test, enregistrez le fichier return_values.py. Ensuite, ouvrez le terminal et exécutez la commande suivante pour exécuter le script Python :
python ~/project/return_values.py

Vous devriez voir une sortie similaire à :

Result as tuple: ('email', 'guido@python.org')
Unpacked name: email
Unpacked value: guido@python.org

Explication :

  • La fonction parse_line divise la chaîne d'entrée au niveau du caractère '=' en utilisant la méthode split. Cette méthode divise la chaîne en parties en fonction du séparateur spécifié.
  • Elle retourne les deux parties sous forme de tuple en utilisant la syntaxe return (name, value). Un tuple est un moyen de regrouper plusieurs valeurs ensemble.
  • Lorsque vous appelez la fonction, vous avez deux options. Vous pouvez soit stocker l'ensemble du tuple dans une seule variable, comme nous l'avons fait avec la variable result. Ou vous pouvez "déballer" le tuple directement dans des variables distinctes en utilisant la syntaxe name, value = parse_line(...). Cela facilite la manipulation des valeurs individuelles.

Ce modèle de retour de plusieurs valeurs sous forme de tuple est très courant en Python. Il rend les fonctions plus polyvalentes car elles peuvent fournir plus d'une information au code qui les appelle.

Retourner des valeurs facultatives

En programmation, il arrive parfois qu'une fonction ne puisse pas générer un résultat valide. Par exemple, lorsqu'une fonction est censée extraire des informations spécifiques d'une entrée, mais que l'entrée n'a pas le format attendu. En Python, une façon courante de gérer de telles situations consiste à retourner None. None est une valeur spéciale en Python qui indique l'absence d'une valeur de retour valide.

Voyons comment nous pouvons modifier une fonction pour gérer les cas où l'entrée ne répond pas aux critères attendus. Nous allons travailler sur la fonction parse_line, qui est conçue pour analyser une ligne au format 'nom=valeur' et retourner à la fois le nom et la valeur.

  1. Mettez à jour la fonction parse_line dans votre fichier return_values.py :
def parse_line(line):
    """
    Parse a line in the format 'name=value' and return both the name and value.
    If the line is not in the correct format, return None.

    Args:
        line (str): Input line to parse in the format 'name=value'

    Returns:
        tuple or None: A tuple containing (name, value) or None if parsing failed
    """
    parts = line.split('=', 1)  ## Split at the first equals sign
    if len(parts) == 2:
        name = parts[0]
        value = parts[1]
        return (name, value)  ## Return as a tuple
    else:
        return None  ## Return None for invalid input

Dans cette fonction parse_line mise à jour, nous divisons d'abord la ligne d'entrée au niveau du premier signe égal en utilisant la méthode split. Si la liste résultante a exactement deux éléments, cela signifie que la ligne est au bon format 'nom=valeur'. Nous extrayons alors le nom et la valeur et les retournons sous forme de tuple. Si la liste n'a pas deux éléments, cela signifie que l'entrée est invalide, et nous retournons None.

  1. Ajoutez du code de test pour démontrer la fonction mise à jour :
## Test the updated parse_line function
if __name__ == "__main__":
    ## Valid input
    result1 = parse_line('email=guido@python.org')
    print(f"Valid input result: {result1}")

    ## Invalid input
    result2 = parse_line('invalid_line_without_equals_sign')
    print(f"Invalid input result: {result2}")

    ## Checking for None before using the result
    test_line = 'user_info'
    result = parse_line(test_line)
    if result is None:
        print(f"Could not parse the line: '{test_line}'")
    else:
        name, value = result
        print(f"Name: {name}, Value: {value}")

Ce code de test appelle la fonction parse_line avec des entrées valides et invalides. Il affiche ensuite les résultats. Notez que lorsque nous utilisons le résultat de la fonction parse_line, nous vérifions d'abord s'il est égal à None. Cela est important car si nous essayons de déballer une valeur None comme si c'était un tuple, nous obtiendrons une erreur.

  1. Enregistrez le fichier et exécutez-le :
python ~/project/return_values.py

Lorsque vous exécutez le script, vous devriez voir une sortie similaire à :

Valid input result: ('email', 'guido@python.org')
Invalid input result: None
Could not parse the line: 'user_info'

Explication :

  • La fonction vérifie maintenant si la ligne contient un signe égal. Cela se fait en divisant la ligne au niveau du signe égal et en vérifiant la longueur de la liste résultante.
  • Si la ligne ne contient pas de signe égal, elle retourne None pour indiquer que l'analyse a échoué.
  • Lorsque vous utilisez une telle fonction, il est important de vérifier si le résultat est égal à None avant de l'utiliser. Sinon, vous risquez de rencontrer des erreurs lorsque vous essayez d'accéder aux éléments d'une valeur None.

Discussion sur la conception :
Une approche alternative pour gérer les entrées invalides consiste à lever une exception. Cette approche est appropriée dans certaines situations :

  1. L'entrée invalide est vraiment exceptionnelle et n'est pas un cas attendu. Par exemple, si l'entrée est censée provenir d'une source de confiance et devrait toujours être au bon format.
  2. Vous voulez forcer l'appelant à gérer l'erreur. En levant une exception, le flux normal du programme est interrompu, et l'appelant doit gérer l'erreur explicitement.
  3. Vous devez fournir des informations d'erreur détaillées. Les exceptions peuvent transporter des informations supplémentaires sur l'erreur, ce qui peut être utile pour le débogage.

Exemple d'une approche basée sur les exceptions :

def parse_line_with_exception(line):
    """Parse a line and raise an exception for invalid input."""
    parts = line.split('=', 1)
    if len(parts) != 2:
        raise ValueError(f"Invalid format: '{line}' does not contain '='")
    return (parts[0], parts[1])

Le choix entre le retour de None et le lancement d'exceptions dépend des besoins de votre application :

  • Retournez None lorsque l'absence de résultat est courante et attendue. Par exemple, lors de la recherche d'un élément dans une liste et qu'il peut ne pas y être.
  • Lancez des exceptions lorsque l'échec est inattendu et devrait interrompre le flux normal. Par exemple, lors de la tentative d'accès à un fichier qui devrait toujours exister.

Travailler avec les objets Future pour la programmation concurrente

En Python, lorsque vous avez besoin d'exécuter des fonctions en même temps, c'est-à-dire de manière concurrente, le langage propose des outils utiles tels que les threads et les processus. Mais vous allez vous heurter à un problème courant : comment obtenir la valeur retournée par une fonction qui s'exécute dans un autre thread ? C'est là que le concept de Future devient très important.

Un objet Future est comme un placeholder pour un résultat qui sera disponible ultérieurement. C'est un moyen de représenter une valeur que produira une fonction à l'avenir, même avant que la fonction n'ait terminé son exécution. Comprenons mieux ce concept grâce à un exemple simple.

Étape 1 : Créer un nouveau fichier

Tout d'abord, vous devez créer un nouveau fichier Python. Nous l'appellerons futures_demo.py. Vous pouvez utiliser la commande suivante dans votre terminal pour créer ce fichier :

touch ~/project/futures_demo.py

Étape 2 : Ajouter le code de base de la fonction

Maintenant, ouvrez le fichier futures_demo.py et ajoutez le code Python suivant. Ce code définit une fonction simple et montre comment fonctionne un appel de fonction normal.

import time
import threading
from concurrent.futures import Future, ThreadPoolExecutor

def worker(x, y):
    """A function that takes time to complete"""
    print('Starting work...')
    time.sleep(5)  ## Simulate a time-consuming task
    print('Work completed')
    return x + y

## Part 1: Normal function call
print("--- Part 1: Normal function call ---")
result = worker(2, 3)
print(f"Result: {result}")

Dans ce code, la fonction worker prend deux nombres, les additionne, mais simule d'abord une tâche longue en suspendant son exécution pendant 5 secondes. Lorsque vous appelez cette fonction de manière normale, le programme attend que la fonction se termine avant d'obtenir la valeur de retour.

Étape 3 : Exécuter le code de base

Enregistrez le fichier et exécutez - le en utilisant la commande suivante dans votre terminal :

python ~/project/futures_demo.py

Vous devriez voir une sortie similaire à ceci :

--- Part 1: Normal function call ---
Starting work...
Work completed
Result: 5

Cela montre qu'un appel de fonction normal attend que la fonction se termine avant de retourner le résultat.

Étape 4 : Exécuter la fonction dans un thread séparé

Ensuite, voyons ce qui se passe lorsque nous exécutons la fonction worker dans un thread séparé. Ajoutez le code suivant au fichier futures_demo.py :

## Part 2: Running in a separate thread (problem: no way to get result)
print("\n--- Part 2: Running in a separate thread ---")
t = threading.Thread(target=worker, args=(2, 3))
t.start()
print("Main thread continues while worker runs...")
t.join()  ## Wait for the thread to complete
print("Worker thread finished, but we don't have its return value!")

Ici, nous utilisons la classe threading.Thread pour démarrer la fonction worker dans un nouveau thread. Le thread principal ne attend pas que la fonction worker se termine et continue son exécution. Cependant, lorsque le thread worker se termine, nous n'avons pas de moyen simple d'obtenir la valeur de retour.

Étape 5 : Exécuter le code avec thread

Enregistrez à nouveau le fichier et exécutez - le en utilisant la même commande :

python ~/project/futures_demo.py

Vous remarquerez que le thread principal continue, le thread worker s'exécute, mais nous ne pouvons pas accéder à la valeur de retour de la fonction worker.

Étape 6 : Utiliser manuellement un objet Future

Pour résoudre le problème d'obtention de la valeur de retour d'un thread, nous pouvons utiliser un objet Future. Ajoutez le code suivant au fichier futures_demo.py :

## Part 3: Using a Future to get the result
print("\n--- Part 3: Using a Future manually ---")

def do_work_with_future(x, y, future):
    """Wrapper that sets the result in the Future"""
    result = worker(x, y)
    future.set_result(result)

## Create a Future object
fut = Future()

## Start a thread that will set the result in the Future
t = threading.Thread(target=do_work_with_future, args=(2, 3, fut))
t.start()

print("Main thread continues...")
print("Waiting for the result...")
## Block until the result is available
result = fut.result()  ## This will wait until set_result is called
print(f"Got the result: {result}")

Dans ce code, nous créons un objet Future et le passons à une nouvelle fonction do_work_with_future. Cette fonction appelle la fonction worker puis définit le résultat dans l'objet Future. Le thread principal peut ensuite utiliser la méthode result() de l'objet Future pour obtenir le résultat lorsque celui - ci est disponible.

Étape 7 : Exécuter le code avec Future

Enregistrez le fichier et exécutez - le à nouveau :

python ~/project/futures_demo.py

Maintenant, vous verrez que nous pouvons obtenir avec succès la valeur de retour de la fonction s'exécutant dans le thread.

Étape 8 : Utiliser ThreadPoolExecutor

La classe ThreadPoolExecutor en Python facilite encore plus le travail avec des tâches concurrentes. Ajoutez le code suivant au fichier futures_demo.py :

## Part 4: Using ThreadPoolExecutor (easier way)
print("\n--- Part 4: Using ThreadPoolExecutor ---")
with ThreadPoolExecutor() as executor:
    ## Submit the work to the executor
    future = executor.submit(worker, 2, 3)

    print("Main thread continues after submitting work...")
    print("Checking if the future is done:", future.done())

    ## Get the result (will wait if not ready)
    result = future.result()
    print("Now the future is done:", future.done())
    print(f"Final result: {result}")

La classe ThreadPoolExecutor s'occupe de créer et de gérer les objets Future pour vous. Vous n'avez qu'à soumettre la fonction et ses arguments, et elle retournera un objet Future que vous pouvez utiliser pour obtenir le résultat.

Étape 9 : Exécuter le code complet

Enregistrez le fichier une dernière fois et exécutez - le :

python ~/project/futures_demo.py

Explication

  1. Appel de fonction normal : Lorsque vous appelez une fonction de manière normale, le programme attend que la fonction se termine et obtient directement la valeur de retour.
  2. Problème avec les threads : Exécuter une fonction dans un thread séparé a un inconvénient. Il n'y a pas de moyen intégré d'obtenir la valeur de retour de la fonction s'exécutant dans ce thread.
  3. Utilisation manuelle d'un objet Future : En créant un objet Future et en le passant au thread, nous pouvons définir le résultat dans l'objet Future puis obtenir le résultat depuis le thread principal.
  4. Utilisation de ThreadPoolExecutor : Cette classe simplifie la programmation concurrente. Elle gère la création et la gestion des objets Future pour vous, ce qui facilite l'exécution concurrente de fonctions et l'obtention de leurs valeurs de retour.

Les objets Future ont plusieurs méthodes utiles :

  • result() : Cette méthode est utilisée pour obtenir le résultat de la fonction. Si le résultat n'est pas encore prêt, elle attendra jusqu'à ce qu'il le soit.
  • done() : Vous pouvez utiliser cette méthode pour vérifier si le calcul de la fonction est terminé.
  • add_done_callback() : Cette méthode vous permet d'enregistrer une fonction qui sera appelée lorsque le résultat sera prêt.

Ce modèle est très important en programmation concurrente, notamment lorsque vous avez besoin d'obtenir des résultats de fonctions s'exécutant en parallèle.

Résumé

Dans ce laboratoire, vous avez appris plusieurs modèles clés pour retourner des valeurs depuis des fonctions en Python. Premièrement, les fonctions Python peuvent retourner plusieurs valeurs en les emballant dans un tuple, permettant un retour et un déballage de valeurs clairs et lisibles. Deuxièmement, pour les fonctions qui ne produisent pas toujours des résultats valides, retourner None est une façon courante d'indiquer l'absence d'une valeur, et le lancement d'exceptions a également été présenté comme une alternative.

Enfin, en programmation concurrente, un objet Future sert de placeholder pour un résultat futur, vous permettant d'obtenir les valeurs de retour de fonctions s'exécutant dans des threads ou des processus séparés. Comprendre ces modèles améliorera la robustesse et la flexibilité de votre code Python. Pour vous entraîner davantage, expérimentez avec différentes stratégies de gestion d'erreurs, utilisez les objets Future avec d'autres types d'exécution concurrente et explorez leur application en programmation asynchrone avec async/await.