Fonctions en Python : Guide Complet pour Débutants

12 min de lecture Python

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 :

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'importance

Valeurs 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)                # 14

print 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.0

Cette 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 :

  1. Local : à l'intérieur de la fonction courante
  2. Enclosing : dans la fonction englobante (fonctions imbriquées)
  3. Global : au niveau du module
  4. 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)  # 1

Mais 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)  # 1

Si 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))  # 10

Cas 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))  # 1

Fibonacci

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 34

Attention : 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 :

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)  # 500000500000

Le @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 : -4

5 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'] -- correct

C'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 liste

5. 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) / 100

Une 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.1

Docstrings

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 :

ConceptSyntaxeQuand l'utiliser
Fonction basiquedef nom(params):Toujours, c'est la base
Valeur par défautdef f(x, y=10):Paramètres optionnels
returnreturn valeurRenvoyer un résultat exploitable
Return multiplereturn a, b, cRenvoyer plusieurs valeurs (tuple)
*argsdef f(*args):Nombre variable d'arguments
**kwargsdef f(**kwargs):Arguments nommés variables
Portée localeVariable dans la fonctionPar défaut, toujours préférer
Lambdalambda x: x * 2Petites fonctions ponctuelles
RécursionLa fonction s'appelle elle-mêmeProblèmes arborescents, fractales
Décorateur@mon_decorateurAjouter du comportement transversal
Type hintsdef 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é :

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.