Introduction
Tu as déjà copié-collé le même bloc de code à cinq endroits différents dans ton programme, puis passé une heure à corriger un bug... cinq fois ? On est tous passés par là. Le copier-coller, c'est le raccourci qui finit toujours par coûter plus cher que le trajet.
Les fonctions sont la solution à ce problème. Elles te permettent d'écrire un bloc de logique une seule fois, de lui donner un nom, et de le réutiliser partout. C'est le premier vrai pas vers un code propre, maintenable et professionnel.
Dans ce guide, tu vas apprendre tout ce qu'il faut savoir sur les fonctions en Python : de la syntaxe de base avec def jusqu'aux décorateurs, en passant par les paramètres, le return, les lambdas et la récursion. Chaque concept est illustré par du code que tu peux copier et exécuter immédiatement.
C'est quoi une fonction en Python ?
Une fonction, c'est une machine réutilisable. Tu lui donnes des ingrédients (les paramètres), elle fait un travail, et elle te rend un résultat (la valeur de retour). Pense à une machine à café : tu mets de l'eau et du café moulu, tu appuies sur un bouton, et tu récupères un espresso. Tu n'as pas besoin de savoir comment fonctionne le mécanisme interne à chaque utilisation.
En Python, tu crées une fonction avec le mot-clé def :
def saluer(prenom): """Affiche un message de bienvenue.""" print(f"Salut {prenom}, bienvenue sur GoGoKodo !")# Appel de la fonctionsaluer("Alice")saluer("Bob")Résultat :
Salut Alice, bienvenue sur GoGoKodo !Salut Bob, bienvenue sur GoGoKodo !Décortiquons cette syntaxe :
- def : mot-clé qui déclare une fonction
- saluer : le nom de la fonction (choisis toujours un nom explicite)
- (prenom) : le paramètre attendu
- : deux-points obligatoires qui ouvrent le bloc
- Le corps de la fonction est indenté (4 espaces par convention)
Tu as déjà utilisé des fonctions sans le savoir : print(), len(), input() sont toutes des fonctions intégrées à Python. Maintenant, tu sais en créer les tiennes.
Paramètres et arguments
Les paramètres sont les variables déclarées dans la définition de la fonction. Les arguments sont les valeurs concrètes que tu passes à l'appel. En pratique, les deux termes sont souvent utilisés de façon interchangeable, mais la distinction existe.
Paramètres positionnels
L'ordre compte. Le premier argument va dans le premier paramètre, le deuxième dans le deuxième, etc.
def presenter(prenom, age, ville): print(f"{prenom}, {age} ans, habite à {ville}")presenter("Alice", 25, "Lyon")# Alice, 25 ans, habite à Lyonpresenter(25, "Lyon", "Alice")# 25, Lyon ans, habite à Alice -- oups, mauvais ordre !Arguments nommés
Pour éviter les erreurs d'ordre, tu peux nommer les arguments à l'appel :
presenter(ville="Lyon", age=25, prenom="Alice")# Alice, 25 ans, habite à Lyon -- l'ordre n'a plus d'importanceValeurs par défaut
Tu peux donner une valeur par défaut à un paramètre. S'il n'est pas fourni à l'appel, Python utilise cette valeur :
def creer_compte(pseudo, role="etudiant", actif=True): return { "pseudo": pseudo, "role": role, "actif": actif }# Utilisation minimalecompte1 = creer_compte("alice42")print(compte1)# {'pseudo': 'alice42', 'role': 'etudiant', 'actif': True}# On surcharge le rôlecompte2 = creer_compte("bob_prof", role="formateur")print(compte2)# {'pseudo': 'bob_prof', 'role': 'formateur', 'actif': True}Règle importante : les paramètres avec valeur par défaut doivent toujours être placés après les paramètres sans défaut. Sinon Python lève une SyntaxError.
Retourner une valeur avec return
Une fonction qui ne fait qu'afficher avec print() est utile pour du débogage, mais en pratique, tu veux qu'elle renvoie un résultat exploitable. C'est le rôle de return.
return vs print
C'est l'une des confusions les plus fréquentes chez les débutants :
# ❌ Avec print : le résultat est affiché mais perdudef addition_print(a, b): print(a + b)resultat = addition_print(3, 4) # Affiche 7print(resultat) # Affiche None !print(resultat * 2) # TypeError: unsupported operand# ✅ Avec return : le résultat est exploitabledef addition_return(a, b): return a + bresultat = addition_return(3, 4) # Rien affiché, mais stocképrint(resultat) # 7print(resultat * 2) # 14print affiche à l'écran. return renvoie la valeur au code appelant. Ce sont deux choses complètement différentes.
Retourner plusieurs valeurs
Python permet de retourner plusieurs valeurs sous forme de tuple :
def analyser_liste(nombres): return min(nombres), max(nombres), sum(nombres) / len(nombres)minimum, maximum, moyenne = analyser_liste([4, 8, 15, 16, 23, 42])print(f"Min: {minimum}, Max: {maximum}, Moyenne: {moyenne:.1f}")# Min: 4, Max: 42, Moyenne: 18.0Cette technique s'appelle le tuple unpacking. Elle est idiomatique en Python et rend le code très lisible.
Note : quand une fonction n'a pas de return explicite, elle retourne None par défaut. C'est souvent la source de bugs silencieux.
*args et **kwargs : paramètres flexibles
Parfois, tu ne sais pas à l'avance combien d'arguments ta fonction va recevoir. C'est là que *args et **kwargs entrent en jeu.
*args : nombre variable d'arguments positionnels
*args collecte tous les arguments positionnels supplémentaires dans un tuple :
def calculer_moyenne(*notes): if not notes: return 0 return sum(notes) / len(notes)print(calculer_moyenne(12, 15, 8)) # 11.666...print(calculer_moyenne(20, 18, 16, 14)) # 17.0print(calculer_moyenne(10)) # 10.0**kwargs : nombre variable d'arguments nommés
**kwargs collecte tous les arguments nommés supplémentaires dans un dictionnaire :
def creer_profil(nom, **infos): profil = {"nom": nom} profil.update(infos) return profilprofil = creer_profil( "Alice", age=25, ville="Lyon", langage_prefere="Python")print(profil)# {'nom': 'Alice', 'age': 25, 'ville': 'Lyon', 'langage_prefere': 'Python'}Combiner les deux
Tu peux utiliser les deux ensemble. L'ordre des paramètres doit être : paramètres normaux, puis *args, puis **kwargs :
def log(niveau, *messages, **metadata): texte = " | ".join(messages) extras = ", ".join(f"{k}={v}" for k, v in metadata.items()) print(f"[{niveau}] {texte} ({extras})")log("INFO", "Connexion réussie", "Page chargée", user="alice", ip="127.0.0.1")# [INFO] Connexion réussie | Page chargée (user=alice, ip=127.0.0.1)En pratique, *args est très courant dans les fonctions wrapper et les décorateurs. **kwargs est utile pour les fonctions de configuration ou quand tu veux transmettre des options à une autre fonction.
Portée des variables : local vs global
La portée (ou scope) détermine où une variable est accessible dans ton code. Mal comprendre la portée, c'est s'exposer à des bugs très frustrants.
La règle LEGB
Python cherche les variables dans cet ordre :
- Local : à l'intérieur de la fonction courante
- Enclosing : dans la fonction englobante (fonctions imbriquées)
- Global : au niveau du module
- Built-in : les fonctions intégrées de Python (print, len, etc.)
Le piège classique
compteur = 0def incrementer(): compteur += 1 # UnboundLocalError !incrémenter()Python voit que tu assignes compteur dans la fonction, donc il la considère comme locale. Mais elle n'a pas encore été définie localement au moment du +=. Pour modifier une variable globale, il faut le déclarer explicitement :
compteur = 0def incrementer(): global compteur compteur += 1incrémenter()print(compteur) # 1Mais attention : utiliser global est généralement déconseillé. Cela rend ton code difficile à suivre et à tester. Préfère passer la valeur en paramètre et la retourner :
# ✅ Approche propre : pas de globaldef incrementer(compteur): return compteur + 1compteur = 0compteur = incrementer(compteur)print(compteur) # 1Si tu utilises des boucles à l'intérieur de tes fonctions, comme un for pour parcourir les paramètres, tu peux approfondir avec notre guide complet sur les boucles for en Python.
Fonctions lambda
Une lambda est une fonction anonyme écrite sur une seule ligne. Elle est utile pour les petites opérations ponctuelles, surtout en combinaison avec des fonctions comme sorted(), map() ou filter().
Syntaxe
# Fonction classiquedef doubler(x): return x * 2# Équivalent en lambdadoubler = lambda x: x * 2print(doubler(5)) # 10Cas d'usage concrets
# Trier une liste de tuples par le deuxième élémentetudiants = [("Alice", 15), ("Bob", 18), ("Charlie", 12)]classement = sorted(etudiants, key=lambda e: e[1], reverse=True)print(classement)# [('Bob', 18), ('Alice', 15), ('Charlie', 12)]# Filtrer les nombres pairsnombres = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]pairs = list(filter(lambda n: n % 2 == 0, nombres))print(pairs) # [2, 4, 6, 8, 10]# Appliquer une transformationprix_ht = [100, 250, 49.90, 1200]prix_ttc = list(map(lambda p: round(p * 1.20, 2), prix_ht))print(prix_ttc) # [120.0, 300.0, 59.88, 1440.0]Limites des lambdas
Les lambdas ne peuvent contenir qu'une seule expression. Pas de boucle, pas de if/else complexe, pas de plusieurs instructions. Si ta logique dépasse une ligne, utilise une fonction normale avec def. Les lambdas sont un outil de confort, pas un remplacement systématique.
Fonctions récursives
Une fonction récursive est une fonction qui s'appelle elle-même. C'est un concept puissant, mais qui demande de la rigueur : sans condition d'arrêt, ta fonction tourne à l'infini (jusqu'à ce que Python lève une RecursionError).
Exemple classique : la factorielle
def factorielle(n): if n <= 1: # Condition d'arrêt return 1 return n * factorielle(n - 1) # Appel récursifprint(factorielle(5)) # 120 (5 * 4 * 3 * 2 * 1)print(factorielle(0)) # 1Fibonacci
def fibonacci(n): if n <= 0: return 0 if n == 1: return 1 return fibonacci(n - 1) + fibonacci(n - 2)# Les 10 premiers termesfor i in range(10): print(fibonacci(i), end=" ")# 0 1 1 2 3 5 8 13 21 34Attention : cette version naïve de Fibonacci est très lente pour les grandes valeurs (complexité exponentielle). Chaque appel génère deux sous-appels. Pour fibonacci(35), Python fait plus de 18 millions d'appels. La solution ? La mémoïsation :
from functools import lru_cache@lru_cache(maxsize=None)def fibonacci_rapide(n): if n <= 0: return 0 if n == 1: return 1 return fibonacci_rapide(n - 1) + fibonacci_rapide(n - 2)print(fibonacci_rapide(100)) # 354224848179261915075 -- instantané !Quand éviter la récursion ?
La récursion est élégante, mais pas toujours le meilleur choix. Évite-la quand :
- Une boucle simple fait le même travail de manière plus lisible
- La profondeur de récursion risque de dépasser la limite de Python (par défaut 1000)
- Tu ne peux pas appliquer de mémoïsation et la complexité explose
Les décorateurs en 5 minutes
Un décorateur, c'est une fonction qui prend une fonction en entrée et retourne une nouvelle fonction enrichie. Ça semble abstrait ? Voici un exemple concret.
Mesurer le temps d'exécution
import timefrom functools import wrapsdef timer(func): @wraps(func) # Préserve le nom et la docstring de la fonction originale def wrapper(*args, **kwargs): debut = time.time() resultat = func(*args, **kwargs) duree = time.time() - debut print(f"{func.__name__} exécutée en {duree:.4f}s") return resultat return wrapper@timerdef calculer_somme(n): """Calcule la somme des n premiers entiers.""" return sum(range(n + 1))resultat = calculer_somme(1_000_000)# calculer_somme exécutée en 0.0312sprint(resultat) # 500000500000Le @timer au-dessus de la fonction est du sucre syntaxique. C'est équivalent à écrire calculer_somme = timer(calculer_somme).
Pourquoi @wraps est important
Sans @wraps, la fonction décorée perd son nom et sa docstring :
# Sans @wrapsprint(calculer_somme.__name__) # "wrapper" -- pas terribleprint(calculer_somme.__doc__) # None# Avec @wrapsprint(calculer_somme.__name__) # "calculer_somme" -- correctprint(calculer_somme.__doc__) # "Calcule la somme des n premiers entiers."Pour aller plus loin, consulte le glossaire Python sur les décorateurs.
Un décorateur pratique : vérifier les arguments
from functools import wrapsdef valider_positif(func): @wraps(func) def wrapper(*args, **kwargs): for arg in args: if isinstance(arg, (int, float)) and arg < 0: raise ValueError(f"Argument négatif interdit : {arg}") return func(*args, **kwargs) return wrapper@valider_positifdef racine_carree(n): return n ** 0.5print(racine_carree(16)) # 4.0print(racine_carree(-4)) # ValueError: Argument négatif interdit : -45 erreurs courantes avec les fonctions
Voici les pièges dans lesquels tombent presque tous les débutants. Repère-les maintenant pour ne jamais les reproduire.
1. Le paramètre par défaut mutable
# ❌ Danger : la liste est partagée entre tous les appelsdef ajouter_element(element, liste=[]): liste.append(element) return listeprint(ajouter_element("a")) # ['a']print(ajouter_element("b")) # ['a', 'b'] -- surprise !# ✅ Solution : utiliser None comme valeur par défautdef ajouter_element(element, liste=None): if liste is None: liste = [] liste.append(element) return listeprint(ajouter_element("a")) # ['a']print(ajouter_element("b")) # ['b'] -- correctC'est l'erreur Python la plus célèbre. La valeur par défaut est évaluée une seule fois à la définition de la fonction, pas à chaque appel.
2. Oublier le return
# ❌ La fonction calcule mais ne retourne riendef calculer_prix_ttc(prix_ht, tva=0.20): prix_ttc = prix_ht * (1 + tva) # return oublié !prix = calculer_prix_ttc(100)print(prix) # Noneprint(prix * 2) # TypeError !# ✅ Ne jamais oublier returndef calculer_prix_ttc(prix_ht, tva=0.20): return prix_ht * (1 + tva)3. Confondre print et return
On l'a vu plus haut, mais ça mérite d'être répété : print() affiche pour l'humain, return renvoie pour le programme. Dans 95% des cas, tu veux return.
4. Modifier une variable globale sans le savoir
# ❌ La liste est modifiée même à l'extérieur de la fonctiondef trier_notes(notes): notes.sort() # Modifie la liste originale ! return notesmes_notes = [15, 8, 12, 18]notes_triees = trier_notes(mes_notes)print(mes_notes) # [8, 12, 15, 18] -- la liste originale est modifiée !# ✅ Travailler sur une copiedef trier_notes(notes): return sorted(notes) # Retourne une nouvelle liste5. Trop de paramètres
# ❌ Difficile à lire et à maintenirdef creer_utilisateur(nom, prenom, email, age, ville, pays, telephone, role, actif, newsletter, langue, theme): ...# ✅ Regrouper dans un dictionnaire ou un dataclassfrom dataclasses import dataclass@dataclassclass Utilisateur: nom: str prenom: str email: str age: int = 0 ville: str = "" role: str = "etudiant"def creer_utilisateur(user: Utilisateur): ...Si ta fonction a plus de 3-4 paramètres, c'est un signal qu'elle en fait probablement trop.
Bonnes pratiques
Ces règles ne sont pas des caprices esthétiques. Elles rendent ton code plus facile à lire, tester et déboguer, surtout quand tu y reviens six mois plus tard.
Noms descriptifs
# ❌ Qu'est-ce que c fait ?def c(x, y): return (x * y) / 100# ✅ On comprend immédiatementdef calculer_pourcentage(montant, taux): return (montant * taux) / 100Une fonction = une tâche
Si tu dois utiliser le mot "et" pour décrire ce que fait ta fonction, elle fait probablement deux choses. Découpe-la.
# ❌ Fait trop de chosesdef traiter_commande(commande): # Valide la commande # Calcule le total # Envoie l'email de confirmation # Met à jour le stock ...# ✅ Chaque fonction a une responsabilitédef valider_commande(commande): ...def calculer_total(commande): ...def envoyer_confirmation(email, commande): ...def mettre_a_jour_stock(articles): ...Type hints
Les annotations de type ne changent rien à l'exécution, mais elles documentent ton code et permettent aux outils de détecter des erreurs :
def calculer_imc(poids: float, taille: float) -> float: """Calcule l'Indice de Masse Corporelle. Args: poids: Poids en kilogrammes. taille: Taille en mètres. Returns: L'IMC arrondi à une décimale. """ return round(poids / (taille ** 2), 1)resultat = calculer_imc(75, 1.80)print(resultat) # 23.1Docstrings
Comme dans l'exemple ci-dessus, ajoute une docstring (triple guillemets) juste après le def. Elle est accessible via help(ma_fonction) et apparaît dans l'auto-complétion de ton éditeur. Tu n'as pas besoin de documenter chaque petite fonction utilitaire, mais les fonctions publiques de ton API méritent une docstring.
Pour en savoir plus sur les conventions et cas avancés, la documentation officielle Python sur les fonctions est une excellente référence.
Récapitulatif
Voici un tableau synthétique de tout ce qu'on a couvert :
| Concept | Syntaxe | Quand l'utiliser |
|---|---|---|
| Fonction basique | def nom(params): | Toujours, c'est la base |
| Valeur par défaut | def f(x, y=10): | Paramètres optionnels |
| return | return valeur | Renvoyer un résultat exploitable |
| Return multiple | return a, b, c | Renvoyer plusieurs valeurs (tuple) |
| *args | def f(*args): | Nombre variable d'arguments |
| **kwargs | def f(**kwargs): | Arguments nommés variables |
| Portée locale | Variable dans la fonction | Par défaut, toujours préférer |
| Lambda | lambda x: x * 2 | Petites fonctions ponctuelles |
| Récursion | La fonction s'appelle elle-même | Problèmes arborescents, fractales |
| Décorateur | @mon_decorateur | Ajouter du comportement transversal |
| Type hints | def f(x: int) -> str: | Documenter et valider les types |
| Docstring | """Description.""" | Documenter les fonctions publiques |
Pour aller plus loin
Les fonctions sont un pilier fondamental de Python. Maintenant que tu maîtrises la syntaxe, les paramètres, le return, les lambdas, la récursion et les décorateurs, tu as tout ce qu'il faut pour écrire du code propre et structuré.
Mais la théorie sans pratique, ça ne vaut pas grand-chose. La meilleure façon de vraiment intégrer ces concepts, c'est de coder. Beaucoup. Sur des exercices progressifs qui te poussent juste assez hors de ta zone de confort.
C'est exactement ce que tu trouveras sur nos ateliers interactifs Python : des exercices guidés, du code exécutable dans le navigateur, et une progression pensée pour ancrer chaque concept en mémoire.
Pour approfondir encore, voici quelques ressources de qualité :
- La documentation officielle Python sur les fonctions pour les détails techniques
- Le guide Real Python pour des explications détaillées en anglais
- Notre article sur comment utiliser l'IA pour apprendre à coder plus vite et accélérer ton apprentissage
Les fonctions sont ton premier pas vers la programmation modulaire. Le suivant ? Les classes et la programmation orientée objet. Mais ça, c'est une autre histoire.