Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 415 minDébutant

Fonctions, modules et import en Python

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • définir une fonction qui reçoit des paramètres et renvoie un résultat ;
  • utiliser les paramètres par défaut et les arguments nommés ;
  • éviter les pièges de la portée et de la valeur par défaut modifiable ;
  • découper un script en modules et les importer.

3 exercices corrigés

Une fonction est un bloc de code nommé, qui reçoit des paramètres et renvoie un résultat ; un module est un fichier .py qui regroupe des fonctions réutilisables, qu'on charge avec import. Ce chapitre montre comment définir une fonction avec def, utiliser les paramètres par défaut et les arguments nommés, documenter et annoter une fonction, éviter deux pièges classiques, puis découper un script en modules avec le fameux if __name__ == "__main__".

Définir et appeler une fonction

python
# fonctions.py : définir et appeler des fonctions
def taux_occupation(utilise: float, total: float) -> float:
    """Renvoie le taux d'occupation en pourcentage."""
    return utilise / total * 100


def niveau(taux: float, alerte: float = 80, critique: float = 90) -> str:
    """Renvoie OK, ALERTE ou CRITIQUE selon les deux seuils."""
    if taux >= critique:
        return "CRITIQUE"
    if taux >= alerte:
        return "ALERTE"
    return "OK"


def extremes(valeurs: list[float]) -> tuple[float, float, float]:
    """Renvoie le minimum, le maximum et la moyenne d'une liste non vide."""
    return min(valeurs), max(valeurs), sum(valeurs) / len(valeurs)


t = taux_occupation(437.5, 500)
print(t, niveau(t))
print(niveau(t, critique=85))            # argument nommé : alerte garde 80
print(niveau(t, alerte=90, critique=95))
mini, maxi, moyenne = extremes([61.0, 87.5, 93.2, 44.8])
print(f"min {mini}, max {maxi}, moyenne {moyenne:.1f}")
text
87.5 ALERTE
CRITIQUE
OK
min 44.8, max 93.2, moyenne 71.6
  • def introduit la fonction, suivie de son nom, de ses paramètres entre parenthèses et des deux-points. Le corps est indenté.
  • return renvoie une valeur et termine la fonction. Une fonction peut avoir plusieurs return, comme niveau() : le premier atteint gagne.
  • Paramètres par défaut. alerte: float = 80 rend le paramètre facultatif : sans valeur fournie, il vaut 80.
  • Arguments nommés. À l'appel, niveau(t, critique=85) fixe un paramètre par son nom et laisse les autres à leur valeur par défaut. C'est plus lisible que niveau(t, 80, 85) : on voit ce que vaut chaque nombre.
  • Plusieurs résultats. extremes() renvoie trois valeurs séparées par des virgules (un tuple, chapitre 5) ; l'appel les range dans trois variables d'un coup.

Le texte entre triples guillemets sous le def est la docstring : la documentation de la fonction, que help(niveau) affiche. Les annotations de type (taux: float, -> str) disent ce que la fonction attend et renvoie. Python ne les vérifie pas à l'exécution, mais ton éditeur s'en sert pour te signaler une erreur avant même de lancer le script. list[float] se lit « liste de flottants ».

Portée des variables

Une variable affectée dans une fonction est locale : elle naît à l'appel et disparaît au retour, sans toucher à une variable du même nom à l'extérieur. Une fonction peut lire une variable globale, mais dès qu'elle l'affecte, Python la considère comme locale dans toute la fonction :

python
# portee.py : une fonction qui modifie une variable globale
total = 0


def ajouter(go):
    total = total + go      # total est locale ici : elle n'a pas encore de valeur
    return total


ajouter(500)
text
Traceback (most recent call last):
  File "/home/etudiant/cours-python/portee.py", line 10, in <module>
    ajouter(500)
    ~~~~~~~^^^^^
  File "/home/etudiant/cours-python/portee.py", line 6, in ajouter
    total = total + go      # total est locale ici : elle n'a pas encore de valeur
            ^^^^^
UnboundLocalError: cannot access local variable 'total' where it is not associated with a value

Le traceback se lit de bas en haut : la dernière ligne donne l'erreur, celles du dessus montrent le chemin, de l'appel à la ligne 10 jusqu'à la ligne 6 dans ajouter (lire un traceback). La correction n'est pas le mot-clé global, qui existe mais rend le code difficile à suivre : passe la valeur en paramètre et renvoie le résultat, total = ajouter(total, 500).

Le piège de la valeur par défaut modifiable

python
# defaut_mutable.py : le piège de la liste par défaut
def ajouter_poste(poste, inventaire=[]):
    inventaire.append(poste)
    return inventaire


print(ajouter_poste("PC-001"))
print(ajouter_poste("PC-002"))      # on attend ['PC-002']...


def ajouter_poste_corrige(poste, inventaire=None):
    if inventaire is None:
        inventaire = []             # une nouvelle liste à chaque appel
    inventaire.append(poste)
    return inventaire


print(ajouter_poste_corrige("PC-001"))
print(ajouter_poste_corrige("PC-002"))
text
['PC-001']
['PC-001', 'PC-002']
['PC-001']
['PC-002']

La valeur par défaut est créée une seule fois, quand Python lit le def, et pas à chaque appel. La liste [] est donc partagée par tous les appels qui n'en fournissent pas : le deuxième appel retrouve PC-001. La règle : jamais de liste ni de dictionnaire comme valeur par défaut. Utilise None, et crée la liste dans la fonction.

Importer un module

Un module est un fichier .py ; un paquet, un dossier de modules. Trois façons d'importer :

python
# imports.py : trois façons d'importer
import math
from datetime import date
import statistics as stats

print(math.sqrt(144), math.ceil(437.5 / 100), math.gcd(84, 36))
print(date(2026, 11, 8).isoformat())
print(stats.median([61.0, 87.5, 93.2, 44.8]))
text
12.0 5 12
2026-11-08
74.25
FormeUsageAppel
import mathImporte le module entiermath.sqrt(144) : on voit d'où vient la fonction
from datetime import dateImporte un nom précisdate(2026, 11, 8)
import statistics as statsImporte sous un aliasstats.median(...)

Évite from math import * : il déverse tous les noms du module dans ton script, et tu ne sais plus d'où vient une fonction. Les imports se placent en tête de fichier : d'abord la bibliothèque standard, puis les paquets installés, puis tes propres modules.

Dans la console, dir(math) liste le contenu d'un module et help(math.gcd) explique une fonction : c'est l'aide syntaxique que tu peux consulter pendant l'épreuve E3.

Écrire ton propre module

Dès qu'un script dépasse une centaine de lignes, ou que deux scripts ont besoin des mêmes fonctions, range ces fonctions dans un module. Voici outils_disque.py :

python
"""outils_disque.py : fonctions réutilisables sur l'occupation des disques."""

SEUIL_ALERTE = 80
SEUIL_CRITIQUE = 90


def taux_occupation(utilise: float, total: float) -> float:
    """Renvoie le taux d'occupation en pourcentage."""
    return utilise / total * 100


def niveau(taux: float) -> str:
    """Renvoie OK, ALERTE ou CRITIQUE selon les seuils du module."""
    if taux >= SEUIL_CRITIQUE:
        return "CRITIQUE"
    if taux >= SEUIL_ALERTE:
        return "ALERTE"
    return "OK"


if __name__ == "__main__":
    # Exécuté seulement quand on lance ce fichier, pas quand on l'importe
    assert taux_occupation(50, 200) == 25
    assert niveau(79.9) == "OK"
    assert niveau(80) == "ALERTE"
    assert niveau(95) == "CRITIQUE"
    print("outils_disque : tests OK")

Et un script qui s'en sert, dans le même dossier :

python
# rapport.py : utilise le module outils_disque, rangé dans le même dossier
from outils_disque import niveau, taux_occupation

volumes = [("srv-ad", 38, 120), ("srv-fichiers", 437.5, 500), ("srv-journaux", 186, 200)]
for nom, utilise, total in volumes:
    taux = taux_occupation(utilise, total)
    print(f"{nom:<14}{taux:6.1f} %  {niveau(taux)}")
text
srv-ad          31.7 %  OK
srv-fichiers    87.5 %  ALERTE
srv-journaux    93.0 %  CRITIQUE

Lancé directement, le module exécute ses tests :

text
outils_disque : tests OK

if name == "main"

Quand Python exécute un fichier, il donne à la variable spéciale __name__ la valeur "__main__" pour le fichier lancé, et le nom du module ("outils_disque") pour un fichier importé. Le bloc if __name__ == "__main__": ne s'exécute donc que si tu lances le fichier lui-même. Tu y mets le code de démarrage d'un script ou, comme ici, des vérifications rapides avec assert. Sans ce test, le simple import outils_disque de rapport.py aurait lancé les tests et affiché leur message.

assert condition ne fait rien si la condition est vraie, et arrête le programme avec une AssertionError sinon. C'est le moyen le plus simple de vérifier une fonction ; le chapitre 15 passe aux vrais tests avec pytest.

Exercices

Exercice 1 sur 3 Pourquoi None ? Réussi À revoir

Ce programme affiche None puis plante. Explique pourquoi et corrige-le.

python
def hotes(prefixe):
    """Nombre d'adresses d'hôtes d'un réseau IPv4 de ce préfixe."""
    nombre = 2 ** (32 - prefixe) - 2


resultat = hotes(26)
print("Un /26 contient", resultat, "hôtes")
print(resultat + 1)
Voir la réponseMasquer la réponse de l'exercice 1
text
Un /26 contient None hôtes
Traceback (most recent call last):
  File "/home/etudiant/cours-python/ex_none.py", line 8, in <module>
    print(resultat + 1)
          ~~~~~~~~~^~~
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

La fonction calcule nombre mais ne le renvoie pas : une fonction sans return renvoie None. print affiche donc None, et None + 1 provoque une TypeError. La correction : remplacer la ligne de calcul par return 2 ** (32 - prefixe) - 2. L'appel hotes(26) renvoie alors 62. Confondre « afficher » (print) et « renvoyer » (return) est l'erreur la plus fréquente des débutants : une fonction de calcul renvoie, c'est l'appelant qui décide d'afficher.

Tu avais trouvé ?

Exercice 2 sur 3 Arguments nommés Réussi À revoir

Avec la fonction niveau(taux, alerte=80, critique=90) de ce chapitre, que renvoient ces appels ?

  1. niveau(85)
  2. niveau(85, 90)
  3. niveau(85, critique=85)
  4. niveau(alerte=70, taux=75)
  5. niveau(95, alerte=99)
Voir la réponseMasquer la réponse de l'exercice 2
  1. "ALERTE" : 85 ≥ 80, mais 85 < 90.
  2. "OK" : 90 est passé en deuxième position, donc à alerte. Les deux seuils valent alors 90, et 85 n'atteint ni l'un ni l'autre.
  3. "CRITIQUE" : 85 ≥ 85.
  4. "ALERTE" : tous les arguments sont nommés, l'ordre ne compte plus ; 75 ≥ 70 et 75 < 90.
  5. "CRITIQUE" : 95 ≥ 90 est testé en premier. Le seuil d'alerte à 99, plus haut que le seuil critique, n'a aucun sens ; une fonction robuste le refuserait avec raise ValueError(...) (chapitre 8).

Tu avais trouvé ?

Exercice 3 sur 3 Un module de conversion Réussi À revoir

Écris un module tailles.py avec une fonction formater_taille(octets) qui renvoie la taille avec l'unité binaire adaptée, avec un chiffre après la virgule : 512 donne "512 o", 1536 donne "1.5 Kio", 5 × 1024³ donne "5.0 Gio". Les unités vont jusqu'au Tio. Ajoute des tests avec assert sous if __name__ == "__main__":.

Voir la réponseMasquer la réponse de l'exercice 3
python
"""tailles.py : afficher une taille en octets avec la bonne unité."""

UNITES = ["o", "Kio", "Mio", "Gio", "Tio"]


def formater_taille(octets: int) -> str:
    """Renvoie la taille avec l'unité binaire adaptée : 1536 -> '1.5 Kio'."""
    taille = octets
    rang = 0
    while taille >= 1024 and rang < len(UNITES) - 1:
        taille = taille / 1024
        rang = rang + 1
    if rang == 0:
        return f"{octets} o"
    return f"{taille:.1f} {UNITES[rang]}"


if __name__ == "__main__":
    assert formater_taille(512) == "512 o"
    assert formater_taille(1536) == "1.5 Kio"
    assert formater_taille(5 * 1024 ** 3) == "5.0 Gio"
    assert formater_taille(3 * 1024 ** 5) == "3072.0 Tio"
    print(formater_taille(437_500_000_000))
    print("tests OK")
text
407.5 Gio
tests OK

On divise par 1024 tant que la taille atteint 1024 et qu'il reste une unité plus grande. La condition rang < len(UNITES) - 1 empêche de sortir de la liste : au-delà du Tio, on reste en Tio (3072.0 Tio pour 3 Pio). 437,5 milliards d'octets font 407,5 Gio : la différence avec les « 437,5 Go » du fabricant vient de là, 1 Go = 10⁹ octets alors que 1 Gio = 2³⁰ octets.

Tu avais trouvé ?

Chapitre terminé !

Fais les 3 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Chaînes, listes et tuples en PythonRevoir le sommaire du cours