Fonctions
Structurez et réutilisez votre code efficacement
Fonctions en Python : organisation et réutilisation du code
Les fonctions sont l’un des concepts fondamentaux de la programmation. Elles permettent de regrouper des séquences d’instructions en blocs réutilisables, d’organiser le code en composants logiques et d’éviter les répétitions. Dans ce tutoriel, nous explorerons en profondeur les fonctions en Python, de la syntaxe de base aux concepts avancés.
Qu’est-ce qu’une fonction ?
Une fonction est un bloc de code nommé qui effectue une tâche spécifique. Les fonctions peuvent accepter des données d’entrée (paramètres), effectuer des opérations sur ces données, et renvoyer un résultat. Elles permettent de :
- Réutiliser du code sans avoir à le réécrire
- Simplifier la maintenance en compartimentant la logique
- Améliorer la lisibilité en divisant un problème complexe en sous-problèmes
- Faciliter les tests en isolant des comportements spécifiques
Définir une fonction simple
En Python, les fonctions sont définies avec le mot-clé def
suivi du nom de la fonction et de parenthèses contenant éventuellement des paramètres.
def saluer():
"""Cette fonction affiche un simple message de salutation."""
print("Bonjour, monde !")
# Appel de la fonction
saluer() # Affiche: Bonjour, monde !
Anatomie d’une fonction
- Définition : Commence par
def
, suivi du nom de la fonction et de parenthèses - Docstring : Chaîne de documentation (optionnelle mais recommandée) entre triples guillemets
- Corps : Code indenté qui constitue la fonction
- Instruction
return
: Permet de renvoyer une valeur (optionnel)
Paramètres et arguments
Les paramètres permettent aux fonctions d’accepter des données en entrée, les rendant plus flexibles et réutilisables.
Paramètres positionnels
def saluer_personne(prenom, nom):
"""Salue une personne par son nom complet."""
print(f"Bonjour, {prenom} {nom} !")
# Appel avec des arguments positionnels
saluer_personne("Marie", "Dupont") # Affiche: Bonjour, Marie Dupont !
Paramètres par défaut
Les paramètres par défaut définissent une valeur qui sera utilisée si aucun argument n’est fourni.
def saluer_avec_formule(nom, formule="Bonjour"):
"""Salue avec une formule personnalisable."""
print(f"{formule}, {nom} !")
# Appels de la fonction
saluer_avec_formule("Jean") # Utilise la formule par défaut
# Affiche: Bonjour, Jean !
saluer_avec_formule("Pierre", "Salut") # Remplace la formule par défaut
# Affiche: Salut, Pierre !
Arguments nommés
Les arguments nommés permettent de spécifier explicitement quel paramètre reçoit quelle valeur.
def decrire_personne(nom, age, profession, ville):
"""Affiche une description d'une personne."""
print(f"{nom}, {age} ans, est {profession} à {ville}.")
# Appel avec arguments nommés (ordre différent de la définition)
decrire_personne(
ville="Paris",
profession="ingénieure",
nom="Sophie",
age=32
)
# Affiche: Sophie, 32 ans, est ingénieure à Paris.
Nombre variable d’arguments
Python permet de définir des fonctions qui acceptent un nombre variable d’arguments.
Arguments positionnels variables (*args
)
def calculer_somme(*nombres):
"""Calcule la somme de tous les nombres fournis."""
resultat = 0
for nombre in nombres:
resultat += nombre
return resultat
# Appels avec différents nombres d'arguments
print(calculer_somme(1, 2)) # 3
print(calculer_somme(1, 2, 3, 4, 5)) # 15
print(calculer_somme()) # 0
Arguments nommés variables (**kwargs
)
def afficher_informations(**infos):
"""Affiche toutes les informations fournies sous forme de paires clé-valeur."""
for cle, valeur in infos.items():
print(f"{cle}: {valeur}")
# Appel avec plusieurs arguments nommés
afficher_informations(
nom="Alice",
age=28,
email="alice@example.com"
)
# Affiche:
# nom: Alice
# age: 28
# email: alice@example.com
Combinaison de types d’arguments
def fonction_complete(arg1, arg2, *args, kwarg1="défaut", **kwargs):
"""Démontre tous les types d'arguments possibles."""
print(f"arg1: {arg1}")
print(f"arg2: {arg2}")
print(f"args: {args}")
print(f"kwarg1: {kwarg1}")
print(f"kwargs: {kwargs}")
fonction_complete(1, 2, 3, 4, 5, kwarg1="personnalisé", x=10, y=20)
# Affiche:
# arg1: 1
# arg2: 2
# args: (3, 4, 5)
# kwarg1: personnalisé
# kwargs: {'x': 10, 'y': 20}
Valeurs de retour
Une fonction peut renvoyer une valeur à l’aide de l’instruction return
. Si aucune instruction return
n’est présente, la fonction renvoie implicitement None
.
Retourner une valeur simple
def carre(nombre):
"""Retourne le carré d'un nombre."""
return nombre ** 2
resultat = carre(4)
print(resultat) # 16
Retourner plusieurs valeurs
def obtenir_dimensions():
"""Retourne la largeur et la hauteur d'un écran."""
largeur = 1920
hauteur = 1080
return largeur, hauteur # Retourne un tuple
dimensions = obtenir_dimensions()
print(dimensions) # (1920, 1080)
# Déballage du tuple retourné
largeur, hauteur = obtenir_dimensions()
print(f"Largeur: {largeur}, Hauteur: {hauteur}") # Largeur: 1920, Hauteur: 1080
Retourner différentes valeurs selon les conditions
def analyser_nombre(n):
"""Analyse un nombre et retourne différentes informations selon sa valeur."""
if n < 0:
return "négatif"
elif n == 0:
return "zéro"
else:
return "positif", "pair" if n % 2 == 0 else "impair"
# Appels avec différentes valeurs
print(analyser_nombre(-5)) # négatif
print(analyser_nombre(0)) # zéro
print(analyser_nombre(7)) # ('positif', 'impair')
print(analyser_nombre(8)) # ('positif', 'pair')
Sortie anticipée avec return
def diviser_securise(a, b):
"""Effectue une division sécurisée."""
if b == 0:
print("Division par zéro impossible")
return None # Sortie anticipée
return a / b
print(diviser_securise(10, 2)) # 5.0
print(diviser_securise(10, 0)) # None (après avoir affiché le message d'erreur)
Portée des variables et espaces de noms
La portée d’une variable détermine où cette variable est accessible dans votre code.
Variables locales et globales
# Variable globale
x = 10
def modifier_variable():
# Variable locale
y = 5
print(f"Dans la fonction, x = {x}") # Accès à la variable globale x
print(f"Dans la fonction, y = {y}")
modifier_variable()
print(f"Hors fonction, x = {x}")
# print(f"Hors fonction, y = {y}") # Erreur : y n'est pas défini
Modifier une variable globale
Pour modifier une variable globale à l’intérieur d’une fonction, il faut utiliser le mot-clé global
.
compteur = 0
def incrementer():
global compteur # Déclare que nous utilisons la variable globale
compteur += 1
print(f"Compteur = {compteur}")
incrementer() # Compteur = 1
incrementer() # Compteur = 2
Variables non locales (closures)
Dans les fonctions imbriquées, le mot-clé nonlocal
permet d’accéder à des variables de la fonction englobante.
def creer_compteur():
"""Crée et retourne une fonction de comptage."""
count = 0
def incrementer():
nonlocal count # Accès à la variable de la fonction englobante
count += 1
return count
return incrementer
mon_compteur = creer_compteur()
print(mon_compteur()) # 1
print(mon_compteur()) # 2
print(mon_compteur()) # 3
Fonctions Lambda (fonctions anonymes)
Les fonctions lambda sont des fonctions anonymes, définies en une seule ligne avec une syntaxe concise.
Syntaxe des fonctions lambda
# Fonction standard
def carre(x):
return x ** 2
# Équivalent avec lambda
carre_lambda = lambda x: x ** 2
print(carre(5)) # 25
print(carre_lambda(5)) # 25
Utilisation typique avec les fonctions d’ordre supérieur
Les lambdas sont souvent utilisées avec des fonctions comme map()
, filter()
et sorted()
.
# Utilisation avec map() pour transformer une liste
nombres = [1, 2, 3, 4, 5]
carres = list(map(lambda x: x ** 2, nombres))
print(carres) # [1, 4, 9, 16, 25]
# Utilisation avec filter() pour filtrer une liste
nombres = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pairs = list(filter(lambda x: x % 2 == 0, nombres))
print(pairs) # [2, 4, 6, 8, 10]
# Utilisation avec sorted() pour trier une liste
etudiants = [
{"nom": "Alice", "note": 85},
{"nom": "Bob", "note": 92},
{"nom": "Charlie", "note": 78}
]
etudiants_tries = sorted(etudiants, key=lambda etudiant: etudiant["note"], reverse=True)
for etudiant in etudiants_tries:
print(f"{etudiant['nom']}: {etudiant['note']}")
# Bob: 92
# Alice: 85
# Charlie: 78
Limitations des fonctions lambda
Les fonctions lambda sont limitées à une seule expression et ne peuvent pas contenir d’instructions multiples ni de structures de contrôle complexes. Pour des fonctions plus complexes, il est préférable d’utiliser la syntaxe def
.
Fonctions récursives
Une fonction récursive est une fonction qui s’appelle elle-même.
Exemple de base : factorielle
def factorielle(n):
"""Calcule la factorielle de n de manière récursive."""
if n <= 1: # Cas de base
return 1
else: # Appel récursif
return n * factorielle(n - 1)
print(factorielle(5)) # 120 (5 * 4 * 3 * 2 * 1)
Exemple avancé : parcours d’une structure imbriquée
def parcourir_structure(structure, niveau=0):
"""Parcourt récursivement une structure de données imbriquée."""
# Afficher la structure actuelle avec indentation selon le niveau
indentation = " " * niveau
print(f"{indentation}Niveau {niveau}: {structure}")
# Si la structure est un dictionnaire ou une liste, parcourir récursivement
if isinstance(structure, dict):
for cle, valeur in structure.items():
print(f"{indentation} Clé: {cle}")
parcourir_structure(valeur, niveau + 1)
elif isinstance(structure, list):
for i, item in enumerate(structure):
print(f"{indentation} Index: {i}")
parcourir_structure(item, niveau + 1)
# Exemple d'utilisation
structure_exemple = {
"personne": {
"nom": "Dupont",
"adresses": [
{"type": "domicile", "ville": "Paris"},
{"type": "travail", "ville": "Lyon"}
]
}
}
parcourir_structure(structure_exemple)
Limites de la récursion
Python limite la profondeur de récursion pour éviter les débordements de pile. Par défaut, cette limite est généralement de 1000 appels.
import sys
print(sys.getrecursionlimit()) # Affiche la limite actuelle (généralement 1000)
# Pour modifier la limite (à utiliser avec précaution)
# sys.setrecursionlimit(2000)
Pour éviter les problèmes de récursion profonde, certains algorithmes récursifs peuvent être réécrits de manière itérative ou en utilisant des techniques comme la mémoïsation.
Décorateurs de fonctions
Les décorateurs sont un concept avancé qui permet de modifier ou d’étendre le comportement des fonctions.
Concept de base
def mon_decorateur(fonction):
def wrapper():
print("Avant l'appel de la fonction")
fonction()
print("Après l'appel de la fonction")
return wrapper
@mon_decorateur
def dire_bonjour():
print("Bonjour !")
dire_bonjour()
# Affiche:
# Avant l'appel de la fonction
# Bonjour !
# Après l'appel de la fonction
Décorateur préservant les arguments
def decorateur_avec_arguments(fonction):
def wrapper(*args, **kwargs):
print(f"Arguments: {args}, Kwargs: {kwargs}")
resultat = fonction(*args, **kwargs)
print(f"Résultat: {resultat}")
return resultat
return wrapper
@decorateur_avec_arguments
def addition(a, b):
return a + b
addition(3, 5)
# Affiche:
# Arguments: (3, 5), Kwargs: {}
# Résultat: 8
Décorateurs avec des paramètres
def repeter(n):
def decorateur(fonction):
def wrapper(*args, **kwargs):
for _ in range(n):
resultat = fonction(*args, **kwargs)
return resultat
return wrapper
return decorateur
@repeter(3)
def dire_bonjour(nom):
print(f"Bonjour, {nom} !")
return nom
dire_bonjour("Alice")
# Affiche 3 fois:
# Bonjour, Alice !
Documentation et annotations de type
Docstrings
Les docstrings (chaînes de documentation) permettent de documenter vos fonctions.
def calculer_moyenne(nombres):
"""
Calcule la moyenne arithmétique d'une liste de nombres.
Args:
nombres (list): Une liste de nombres (int ou float)
Returns:
float: La moyenne des nombres
Raises:
ZeroDivisionError: Si la liste est vide
Examples:
>>> calculer_moyenne([1, 2, 3, 4, 5])
3.0
"""
return sum(nombres) / len(nombres)
Les docstrings peuvent être consultées avec la fonction help()
:
help(calculer_moyenne)
Annotations de type (Python 3.5+)
Les annotations de type améliorent la lisibilité et permettent la vérification statique du code.
def saluer(nom: str, age: int = 30) -> str:
"""Retourne un message de salutation personnalisé."""
return f"Bonjour {nom}, vous avez {age} ans !"
# Les annotations sont stockées dans l'attribut __annotations__
print(saluer.__annotations__)
# {'nom': <class 'str'>, 'age': <class 'int'>, 'return': <class 'str'>}
Pour les types plus complexes, utilisez le module typing
:
from typing import List, Dict, Tuple, Optional, Union, Callable
def traiter_donnees(
valeurs: List[int],
config: Dict[str, str],
limites: Tuple[int, int],
callback: Optional[Callable[[int], bool]] = None
) -> Union[int, str]:
"""Traite des données avec des types complexes."""
# Implémentation...
return 0
Bonnes pratiques pour les fonctions
Principes généraux
-
Une fonction, une responsabilité : Chaque fonction devrait avoir un objectif clair et unique.
-
Noms descriptifs : Choisissez des noms qui expliquent ce que fait la fonction, pas comment elle le fait.
# Bon def calculer_moyenne(nombres): return sum(nombres) / len(nombres) # À éviter def fonction1(liste): return sum(liste) / len(liste)
-
Taille raisonnable : Les fonctions devraient être relativement courtes (généralement moins de 20-30 lignes).
-
Paramètres par défaut immuables : Évitez d’utiliser des objets mutables comme valeurs par défaut.
# Problématique def ajouter_element(element, liste=[]): liste.append(element) return liste # Meilleure approche def ajouter_element(element, liste=None): if liste is None: liste = [] liste.append(element) return liste
-
Documentation claire : Utilisez des docstrings pour expliquer le but, les paramètres et les valeurs de retour.
Convention de nommage
Suivez la PEP 8 pour les conventions de nommage :
- Utilisez des noms en minuscules séparés par des underscores (
snake_case
) - Préférez les verbes pour les fonctions qui effectuent des actions
- Soyez cohérent dans vos conventions
# Bon
def calculer_moyenne(nombres):
pass
def obtenir_utilisateur_par_id(user_id):
pass
def est_valide(donnee):
pass
Exemple complet : Application des fonctions
Voici un exemple qui combine plusieurs concepts de fonctions pour créer un système simple de gestion de tâches :
from typing import List, Dict, Optional, Tuple, Callable
import time
# Types personnalisés
Tache = Dict[str, any]
ListeTaches = List[Tache]
def creer_tache(
titre: str,
description: str = "",
priorite: int = 1
) -> Tache:
"""
Crée une nouvelle tâche avec un horodatage automatique.
Args:
titre: Le titre de la tâche
description: Une description détaillée (optionnelle)
priorite: Niveau de priorité (1-5, 5 étant le plus important)
Returns:
Un dictionnaire représentant la tâche
"""
return {
"id": int(time.time()),
"titre": titre,
"description": description,
"priorite": max(1, min(5, priorite)), # Garantit que la priorité est entre 1 et 5
"termine": False,
"date_creation": time.strftime("%Y-%m-%d %H:%M:%S")
}
def ajouter_tache(
liste_taches: ListeTaches,
tache: Tache
) -> ListeTaches:
"""Ajoute une tâche à la liste et retourne la liste mise à jour."""
liste_taches.append(tache)
return liste_taches
def marquer_comme_termine(
liste_taches: ListeTaches,
tache_id: int
) -> Tuple[bool, str]:
"""
Marque une tâche comme terminée.
Returns:
Un tuple (succès, message)
"""
for tache in liste_taches:
if tache["id"] == tache_id:
tache["termine"] = True
return True, f"Tâche '{tache['titre']}' marquée comme terminée"
return False, "Tâche non trouvée"
def filtrer_taches(
liste_taches: ListeTaches,
termine: Optional[bool] = None,
priorite_min: int = 0
) -> ListeTaches:
"""
Filtre les tâches selon plusieurs critères.
Args:
liste_taches: Liste des tâches à filtrer
termine: Si spécifié, filtre les tâches selon leur état
priorite_min: Filtre les tâches avec une priorité supérieure ou égale
Returns:
Une nouvelle liste contenant les tâches filtrées
"""
resultat = []
for tache in liste_taches:
# Vérifier l'état de la tâche si le paramètre est spécifié
if termine is not None and tache["termine"] != termine:
continue
# Vérifier la priorité minimale
if tache["priorite"] < priorite_min:
continue
resultat.append(tache)
return resultat
def afficher_taches(
liste_taches: ListeTaches,
formatter: Callable[[Tache], str] = None
) -> None:
"""
Affiche une liste de tâches.
Args:
liste_taches: Liste des tâches à afficher
formatter: Fonction optionnelle pour formater chaque tâche
"""
if not liste_taches:
print("Aucune tâche à afficher.")
return
# Formatter par défaut
if formatter is None:
def formatter(tache):
statut = "✓" if tache["termine"] else "☐"
return f"{statut} [{tache['priorite']}] {tache['titre']}"
# Afficher chaque tâche
for i, tache in enumerate(liste_taches, 1):
print(f"{i}. {formatter(tache)}")
# Utilisation de nos fonctions
if __name__ == "__main__":
mes_taches = []
# Ajouter quelques tâches
mes_taches = ajouter_tache(
mes_taches,
creer_tache("Apprendre les fonctions Python", "Tutoriel complet", 4)
)
mes_taches = ajouter_tache(
mes_taches,
creer_tache("Faire les courses")
)
mes_taches = ajouter_tache(
mes_taches,
creer_tache("Répondre aux emails", priorite=5)
)
# Afficher les tâches
print("Toutes les tâches:")
afficher_taches(mes_taches)
# Marquer une tâche comme terminée
succes, message = marquer_comme_termine(mes_taches, mes_taches[1]["id"])
print(f"\nRésultat: {message}")
# Filtrer et afficher les tâches non terminées avec priorité >= 4
taches_importantes = filtrer_taches(mes_taches, termine=False, priorite_min=4)
print("\nTâches importantes non terminées:")
afficher_taches(taches_importantes)
# Utiliser un formateur personnalisé
print("\nAffichage détaillé:")
afficher_taches(mes_taches, lambda t: f"{t['titre']} - Priorité: {t['priorite']} - Créée le: {t['date_creation']}")
Conclusion
Les fonctions sont l’un des piliers de la programmation en Python. Elles permettent de structurer votre code, de le rendre plus modulaire, plus lisible et plus facile à maintenir. En maîtrisant les différentes techniques présentées dans ce tutoriel, vous pourrez :
- Diviser des problèmes complexes en sous-problèmes gérables
- Éviter la duplication de code
- Créer des bibliothèques de fonctions réutilisables
- Écrire du code plus expressif et maintenable
Dans le prochain tutoriel, nous explorerons les modules et packages en Python, qui vous permettront d’organiser vos fonctions et classes en unités logiques et réutilisables.
Besoin de réviser les structures de données? Consultez notre tutoriel sur les structures de données en Python.
Commentaires
Les commentaires sont alimentés par GitHub Discussions
Connectez-vous avec GitHub pour participer à la discussion