Aller au contenu principal

Les Fonctions en Python

Pourquoi utiliser des fonctions ?

Imaginez que vous écrivez un programme qui calcule la TVA plusieurs fois. Sans fonctions, vous copiez-collez le même code partout. C'est le cauchemar du développeur : quand il faut corriger un bug, il faut le faire à dix endroits différents.

Les fonctions résument quatre principes fondamentaux :

  • DRY (Don't Repeat Yourself) : écrire le code une seule fois, l'appeler autant de fois que nécessaire
  • Réutilisabilité : une fonction bien écrite peut être utilisée dans plusieurs projets
  • Lisibilité : calculer_tva(prix) est bien plus clair que cinq lignes de calcul inline
  • Testabilité : on peut tester une fonction isolément, indépendamment du reste du programme
# Sans fonction : répétition et risque d'erreur
prix1 = 100
tva1 = prix1 * 0.20
total1 = prix1 + tva1

prix2 = 250
tva2 = prix2 * 0.20
total2 = prix2 + tva2

# Avec une fonction : propre et reutilisable
def prix_ttc(prix_ht):
tva = prix_ht * 0.20
return prix_ht + tva

total1 = prix_ttc(100)
total2 = prix_ttc(250)

Le mot-clé def et la syntaxe de base

En Python, on définit une fonction avec le mot-clé def, suivi du nom de la fonction, des parenthèses, et d'un deux-points. Le corps de la fonction doit être indenté (4 espaces par convention).

def nom_de_la_fonction(parametre1, parametre2):
# corps de la fonction
instruction1
instruction2

Exemple concret :

def saluer(prenom):
message = "Bonjour, " + prenom + " !"
print(message)

Règles importantes :

  • Le nom doit être en minuscules avec des underscores (snake_case)
  • L'indentation est obligatoire (Python l'utilise pour délimiter les blocs)
  • La fonction n'est pas exécutée à sa définition, seulement quand on l'appelle

Appeler une fonction

Pour exécuter une fonction, on écrit son nom suivi de parenthèses avec les arguments :

def saluer(prenom):
print("Bonjour, " + prenom + " !")

# Appel de la fonction
saluer("Alice") # Affiche : Bonjour, Alice !
saluer("Bob") # Affiche : Bonjour, Bob !
saluer("Marie") # Affiche : Bonjour, Marie !

Une fonction peut être appelée autant de fois que nécessaire, avec des arguments différents à chaque fois.


Le mot-clé return

return permet à une fonction de renvoyer une valeur au code appelant. Sans return, la fonction retourne None (la valeur "rien" en Python).

def additionner(a, b):
return a + b

resultat = additionner(3, 5)
print(resultat) # 8

# Utilisation directe dans une expression
print(additionner(10, 20) * 2) # 60

Pas de return = retourne None

def afficher_carre(n):
print(n ** 2) # affiche mais ne retourne rien

valeur = afficher_carre(4) # affiche 16
print(valeur) # None

return peut aussi interrompre l'exécution de la fonction :

def diviser(a, b):
if b == 0:
return None # arret immediat si division par zero
return a / b

print(diviser(10, 2)) # 5.0
print(diviser(10, 0)) # None

Paramètres et arguments positionnels

Les paramètres sont les variables déclarées dans la définition de la fonction. Les arguments sont les valeurs passées lors de l'appel.

def presenter(prenom, age, ville):
print(f"{prenom} a {age} ans et habite a {ville}.")

presenter("Alice", 25, "Paris")
# Alice a 25 ans et habite a Paris.

L'ordre des arguments positionnels est important : le premier argument va au premier paramètre, etc.


Valeurs par défaut des paramètres

On peut définir une valeur par défaut pour un paramètre. Si l'appelant ne fournit pas cet argument, la valeur par défaut est utilisée.

def saluer(prenom, salutation="Bonjour"):
print(f"{salutation}, {prenom} !")

saluer("Alice") # Bonjour, Alice !
saluer("Bob", "Bonsoir") # Bonsoir, Bob !
saluer("Marie", "Salut") # Salut, Marie !

Attention : les paramètres avec valeur par défaut doivent toujours être placés après les paramètres sans valeur par défaut.

# Correct
def f(a, b, c=10):
pass

# Erreur de syntaxe !
# def f(a, b=10, c):
# pass

Arguments nommés (keyword arguments)

On peut passer les arguments par leur nom, ce qui permet de changer leur ordre et rend le code plus lisible :

def presenter(prenom, age, ville):
print(f"{prenom}, {age} ans, {ville}")

# Arguments nommes : l'ordre n'a pas d'importance
presenter(age=30, ville="Lyon", prenom="Claire")
# Claire, 30 ans, Lyon

# Mix positionnels et nommes (positionnels d'abord)
presenter("Paul", ville="Bordeaux", age=22)
# Paul, 22 ans, Bordeaux

*args : arguments positionnels variables

Quand on ne connaît pas à l'avance le nombre d'arguments, on utilise *args. Python regroupe tous les arguments supplémentaires dans un tuple.

def additionner_tout(*nombres):
total = 0
for n in nombres:
total += n
return total

print(additionner_tout(1, 2, 3)) # 6
print(additionner_tout(10, 20, 30, 40)) # 100
print(additionner_tout(5)) # 5
def afficher_infos(titre, *elements):
print(f"=== {titre} ===")
for el in elements:
print(f" - {el}")

afficher_infos("Courses", "pommes", "pain", "lait", "fromage")
# === Courses ===
# - pommes
# - pain
# - lait
# - fromage

**kwargs : arguments nommés variables

**kwargs capture les arguments nommés supplémentaires dans un dictionnaire :

def afficher_profil(**infos):
for cle, valeur in infos.items():
print(f"{cle} : {valeur}")

afficher_profil(prenom="Alice", age=25, ville="Paris", metier="dev")
# prenom : Alice
# age : 25
# ville : Paris
# metier : dev

Combinaison de tout :

def fonction_complete(a, b, *args, **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"kwargs={kwargs}")

fonction_complete(1, 2, 3, 4, 5, x=10, y=20)
# a=1, b=2
# args=(3, 4, 5)
# kwargs={'x': 10, 'y': 20}

Portée des variables (scope)

Les variables définies à l'intérieur d'une fonction sont locales : elles n'existent que pendant l'exécution de la fonction et disparaissent ensuite.

def ma_fonction():
variable_locale = 42
print(variable_locale) # 42

ma_fonction()
# print(variable_locale) # NameError ! variable_locale n'existe pas ici

Les variables définies en dehors des fonctions sont globales et accessibles en lecture dans les fonctions :

compteur = 0

def afficher_compteur():
print(compteur) # lecture OK

afficher_compteur() # 0

Pour modifier une variable globale depuis une fonction, il faut utiliser le mot-clé global :

compteur = 0

def incrementer():
global compteur
compteur += 1

incrementer()
incrementer()
print(compteur) # 2
Attention

L'utilisation de global est souvent signe d'une mauvaise conception. Préférez retourner des valeurs plutôt que de modifier des variables globales.


Docstrings : documenter ses fonctions

Une docstring est une chaîne de caractères placée juste après la définition de la fonction, entre triple guillemets. Elle décrit ce que fait la fonction.

def calculer_tva(prix_ht, taux=0.20):
"""
Calcule le prix TTC a partir du prix HT.

Args:
prix_ht (float): Le prix hors taxes.
taux (float): Le taux de TVA (defaut 20%).

Returns:
float: Le prix toutes taxes comprises.
"""
return prix_ht * (1 + taux)

# Accessible via help()
help(calculer_tva)

Les docstrings sont utilisées par les IDE pour l'autocomplétion et par des outils comme Sphinx pour générer de la documentation automatiquement.


Fonctions lambda

Une lambda est une fonction anonyme écrite sur une seule ligne. Elle est utile pour des opérations simples et courtes, souvent passées en argument d'autres fonctions.

# Syntaxe : lambda paramètres: expression
carre = lambda x: x ** 2
print(carre(5)) # 25

additionner = lambda a, b: a + b
print(additionner(3, 4)) # 7

est_pair = lambda n: n % 2 == 0
print(est_pair(4)) # True
print(est_pair(7)) # False

Les lambdas ne peuvent contenir qu'une seule expression (pas de return, pas de boucles, pas de if/else multi-lignes).


Fonctions d'ordre supérieur : map(), filter(), sorted()

Python permet de passer des fonctions comme arguments d'autres fonctions. C'est la programmation fonctionnelle.

map(fonction, iterable)

Applique une fonction à chaque élément d'un itérable :

nombres = [1, 2, 3, 4, 5]

carres = list(map(lambda x: x ** 2, nombres))
print(carres) # [1, 4, 9, 16, 25]

# Equivalence avec une liste comprehension :
carres = [x ** 2 for x in nombres]

filter(fonction, iterable)

Garde uniquement les éléments pour lesquels la fonction retourne True :

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]

grands = list(filter(lambda x: x > 5, nombres))
print(grands) # [6, 7, 8, 9, 10]

sorted(iterable, key=fonction)

Trie un itérable en utilisant une fonction comme clé de tri :

etudiants = [("Alice", 17), ("Bob", 15), ("Claire", 19), ("David", 16)]

# Trier par note (deuxième élément du tuple)
par_note = sorted(etudiants, key=lambda e: e[1])
print(par_note)
# [('Bob', 15), ('David', 16), ('Alice', 17), ('Claire', 19)]

# Tri inverse
par_note_desc = sorted(etudiants, key=lambda e: e[1], reverse=True)
print(par_note_desc)
# [('Claire', 19), ('Alice', 17), ('David', 16), ('Bob', 15)]

Récursion

Une fonction est récursive quand elle s'appelle elle-même. Toute fonction récursive doit avoir :

  1. Un cas de base : la condition d'arrêt (sinon boucle infinie)
  2. Un cas récursif : l'appel à elle-même avec un problème plus petit

Exemple : factorielle

5! = 5 × 4 × 3 × 2 × 1 = 120
5! = 5 × 4!
4! = 4 × 3!
...
1! = 1 (cas de base)
def factorielle(n):
if n <= 1: # cas de base
return 1
return n * factorielle(n - 1) # cas recursif

print(factorielle(5)) # 120
print(factorielle(10)) # 3628800
# Trace de l'execution de factorielle(4) :
# factorielle(4)
# = 4 * factorielle(3)
# = 4 * 3 * factorielle(2)
# = 4 * 3 * 2 * factorielle(1)
# = 4 * 3 * 2 * 1
# = 24
Limite de recursion

Python limite la profondeur de récursion à 1000 par défaut pour éviter les débordements de pile. Pour les grands calculs, préférez une version itérative (avec une boucle).


Exercices pratiques

Exercice 1 : Définir une fonction avec return

Bonne pratique - Nommage des fonctions

Utilisez des verbes ou des noms descriptifs pour nommer vos fonctions. calculer_tva(), obtenir_age(), est_valide() sont de bons noms. Évitez f(), func() ou traitement().


Exercice 2 : Fonction avec paramètre par défaut

Bonne pratique - Valeurs par défaut

Utilisez des valeurs par défaut pour les paramètres optionnels communs. Cela simplifie l'appel de la fonction dans le cas le plus fréquent, tout en offrant de la flexibilité pour les cas particuliers.


Exercice 3 : Fonction lambda

Bonne pratique - Lambda vs def

Utilisez les lambdas uniquement pour des expressions courtes et simples, notamment comme argument de map(), filter() ou sorted(). Pour toute logique plus complexe, définissez une fonction normale avec def : c'est plus lisible et plus facile à tester.


Exercice 4 : Utiliser map() pour appliquer une fonction à une liste

Bonne pratique - map() vs comprehension

map() et les listes en compréhension font la même chose. En Python moderne, les listes en compréhension ([x**2 for x in nombres]) sont généralement préférées car plus lisibles. Utilisez map() quand vous passez une fonction existante déjà nommée.


Exercice 5 : Utiliser filter() pour garder les éléments pairs

Bonne pratique - filter() vs comprehension

De même que pour map(), une liste en compréhension avec condition ([x for x in nombres if x % 2 == 0]) est souvent plus lisible que filter(). Privilégiez la lisibilité dans votre code Python.


Exercice 6 : Fonction récursive (factorielle)

Bonne pratique - Recursion

Identifiez toujours clairement le cas de base avant d'écrire le cas récursif. Sans cas de base valide, la fonction récurse indéfiniment jusqu'à une erreur RecursionError. Pour les grandes valeurs, une boucle for ou while est souvent plus efficace.


Quiz de révision


Que retourne une fonction sans instruction return ?


Quel est le résultat de list(map(lambda x: x*2, [1, 2, 3])) ?


Dans def f(a, b=5, *args, **kwargs), quel type est args ?


Qu'est-ce qu'un cas de base dans une fonction récursive ?


Que fait le mot-clé global dans une fonction ?


📌 Une solution