Aller au contenu principal

Les Fonctions en Python

Pourquoi utiliser des fonctions ?

Imaginez que vous ecrivez un programme qui calcule la TVA plusieurs fois. Sans fonctions, vous copiez-collez le meme code partout. C'est le cauchemar du developpeur : quand il faut corriger un bug, il faut le faire a dix endroits differents.

Les fonctions resument quatre principes fondamentaux :

  • DRY (Don't Repeat Yourself) : ecrire le code une seule fois, l'appeler autant de fois que necessaire
  • Reutilisabilite : une fonction bien ecrite peut etre utilisee dans plusieurs projets
  • Lisibilite : calculer_tva(prix) est bien plus clair que cinq lignes de calcul inline
  • Testabilite : on peut tester une fonction isolement, independamment du reste du programme
# Sans fonction : repetition 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-cle def et la syntaxe de base

En Python, on definit une fonction avec le mot-cle def, suivi du nom de la fonction, des parentheses, et d'un deux-points. Le corps de la fonction doit etre indente (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)

Regles importantes :

  • Le nom doit etre en minuscules avec des underscores (snake_case)
  • L'indentation est obligatoire (Python l'utilise pour delimiter les blocs)
  • La fonction n'est pas executee a sa definition, seulement quand on l'appelle

Appeler une fonction

Pour executer une fonction, on ecrit son nom suivi de parentheses 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 etre appelee autant de fois que necessaire, avec des arguments differents a chaque fois.


Le mot-cle return

return permet a 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'execution 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

Parametres et arguments positionnels

Les parametres sont les variables declarees dans la definition de la fonction. Les arguments sont les valeurs passees 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 parametre, etc.


Valeurs par defaut des parametres

On peut definir une valeur par defaut pour un parametre. Si l'appelant ne fournit pas cet argument, la valeur par defaut est utilisee.

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 parametres avec valeur par defaut doivent toujours etre places apres les parametres sans valeur par defaut.

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

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

Arguments nommes (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 connait pas a l'avance le nombre d'arguments, on utilise *args. Python regroupe tous les arguments supplementaires 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 nommes variables

**kwargs capture les arguments nommes supplementaires 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}

Portee des variables (scope)

Les variables definies a l'interieur d'une fonction sont locales : elles n'existent que pendant l'execution 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 definies 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-cle 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. Preferez retourner des valeurs plutot que de modifier des variables globales.


Docstrings : documenter ses fonctions

Une docstring est une chaine de caracteres placee juste apres la definition de la fonction, entre triple guillemets. Elle decrit 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 utilisees par les IDE pour l'autocompletion et par des outils comme Sphinx pour generer de la documentation automatiquement.


Fonctions lambda

Une lambda est une fonction anonyme ecrite sur une seule ligne. Elle est utile pour des operations simples et courtes, souvent passees en argument d'autres fonctions.

# Syntaxe : lambda parametres: 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 superieur : 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 a chaque element d'un iterable :

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 elements 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 iterable en utilisant une fonction comme cle de tri :

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

# Trier par note (deuxieme element 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)]

Recursion

Une fonction est recursive quand elle s'appelle elle-meme. Toute fonction recursive doit avoir :

  1. Un cas de base : la condition d'arret (sinon boucle infinie)
  2. Un cas recursif : l'appel a elle-meme avec un probleme 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 recursion a 1000 par defaut pour eviter les debordements de pile. Pour les grands calculs, preferez une version iterative (avec une boucle).


Exercices pratiques

Exercice 1 : Definir 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. Evitez f(), func() ou traitement().


Exercice 2 : Fonction avec parametre par defaut

Bonne pratique - Valeurs par defaut

Utilisez des valeurs par defaut pour les parametres optionnels communs. Cela simplifie l'appel de la fonction dans le cas le plus frequent, tout en offrant de la flexibilite 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, definissez une fonction normale avec def : c'est plus lisible et plus facile a tester.


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

Bonne pratique - map() vs comprehension

map() et les listes en comprehension font la meme chose. En Python moderne, les listes en comprehension ([x**2 for x in nombres]) sont generalement preferees car plus lisibles. Utilisez map() quand vous passez une fonction existante deja nommee.


Exercice 5 : Utiliser filter() pour garder les elements pairs

Bonne pratique - filter() vs comprehension

De meme que pour map(), une liste en comprehension avec condition ([x for x in nombres if x % 2 == 0]) est souvent plus lisible que filter(). Privilegiez la lisibilite dans votre code Python.


Exercice 6 : Fonction recursive (factorielle)

Bonne pratique - Recursion

Identifiez toujours clairement le cas de base avant d'ecrire le cas recursif. Sans cas de base valide, la fonction recurse indefiniment jusqu'a une erreur RecursionError. Pour les grandes valeurs, une boucle for ou while est souvent plus efficace.


Quiz de revision


Que retourne une fonction sans instruction return ?


Quel est le resultat 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 recursive ?


Que fait le mot-cle global dans une fonction ?


Une solution