Aller au contenu
English

Modules et importations

  • Leçon 8
  • 35 min
  • Module 3 : Fonctions et structure du programme
Leçon 8 sur 18 · Python pour l'ingénierie de l'IA en pratiqueModules et importations

Ce que vous saurez faire

Déplacez les fonctions des billets de soutien dans un paquet nommé ticket_cleaner, puis importez-les dans le script. Deux scripts peuvent alors utiliser les mêmes fonctions. Dans la suite du cours, chaque nouvelle partie aura son propre module.

Carnet JupyterFaites cette leçon dans un carnet JupyterColab · Kaggle · GitHub · en anglais

Le carnet (notebook) Jupyter contient chaque étape, tâche et vérification de cette leçon, avec les consignes. Ses premières cellules créent le dossier ticket-cleaner tel qu’il est au début de cette leçon. Il fonctionne donc sans votre projet local. Lisez les explications ici et exécutez le code dans le carnet. Aucune installation n’est nécessaire.

Le carnet est en anglais. Colab et Kaggle ouvrent votre propre copie. Il faut un compte Google ou Kaggle gratuit. Vous êtes bloqué ? Consultez le carnet de solution.

Avant de commencerCe qu’il faut savoir et avoir sous la mainEnviron 35 minutes

Ce que vous devez savoir

Ce qu’il vous faut sur votre ordinateur (seulement pour le parcours local; le carnet et les encadrés de cette page n’en ont pas besoin)

Testé avec Python 3.14.6 sur macOS. Le carnet a été testé dans Jupyter.

Un module est un fichier de code Python que d’autres parties du code peuvent importer. Chaque fichier .py est un module. Son nom est celui du fichier sans .py : le fichier report.py est le module report.

Vous importez déjà des modules fournis avec Python : import sys, import platform. L’instruction import exécute une fois le fichier du module, puis vous donne accès à ses fonctions et variables. Il existe deux formes :

import platform
print(platform.python_version()) # module name, dot, function name
from platform import python_version
print(python_version()) # the function name only

Les deux formes exécutent le même fichier. La première garde le nom du module devant la fonction. Le lecteur voit ainsi d’où vient python_version. La seconde est plus courte. Utilisez la seconde forme pour quelques noms que vous utilisez souvent.

Un paquet est un dossier de modules qui contient un fichier nommé __init__.py. Le fichier __init__.py indique à Python que le dossier est un paquet. Il peut être vide. Vous importez un module d’un paquet avec un point. from ticket_cleaner.report import count_by_category signifie « dans le paquet ticket_cleaner, le module report, la fonction count_by_category ».

À la fin de cette leçon, le projet ressemble à ceci :

ticket-cleaner/
ticket-cleaner/
├── .venv/
├── count_tickets.py ← the script that you run
├── requirements.txt
├── requirements-lock.txt
└── ticket_cleaner/ ← the package: code that other code imports
├── __init__.py
└── report.py ← filter_by_status, count_by_category, average_priority

Le nom du dossier du projet contient un - (ticket-cleaner), et celui du paquet contient un _ (ticket_cleaner). C’est volontaire. Le nom d’un module ou d’un paquet doit être un nom Python valide. Le caractère - n’est pas permis dans un nom Python.

Le script garde seulement la partie propre à ce programme : les données et main(). Les fonctions passent dans report.py, car tout programme qui produit des rapports sur les billets peut les utiliser.

Quand Python exécute import ticket_cleaner, il le cherche dans une liste de dossiers, dans cet ordre :

  1. Le dossier du script que vous exécutez. Pour python count_tickets.py, c’est ticket-cleaner/.
  2. Les dossiers des modules de Python, comme sys et csv.
  3. Les paquets que vous avez installés dans l’environnement actif, comme httpx.

Python retient la première correspondance. Cet ordre entraîne deux problèmes. D’abord, le paquet doit se trouver dans le dossier du script que vous exécutez. Vous exécutez donc count_tickets.py, qui se trouve à côté de ticket_cleaner/. Ensuite, un de vos fichiers peut masquer un module Python s’ils portent le même nom. L’erreur fréquente à la fin de cette leçon le montre.

Une importation exécute chaque ligne du module, une seule fois. Une ligne def définit seulement une fonction. Un module qui contient uniquement des fonctions ne fait donc rien de visible quand vous l’importez. Un module qui contient un print ou un appel au niveau supérieur l’exécute pendant l’importation.

Chaque module possède une variable __name__. Quand vous exécutez un fichier directement, sa variable __name__ vaut "__main__". Quand le fichier est importé, __name__ contient le nom du module. Voici un module qui affiche son nom, exécuté de deux façons :

ticket_cleaner/report.py (a test line at the end)
print("report.py name:", __name__)
Terminal
python ticket_cleaner/report.py
python -c "import ticket_cleaner.report"
Sortie
report.py name: __main__
report.py name: ticket_cleaner.report

Vous pouvez donc dire « exécutez ceci seulement quand le fichier est le programme, pas quand il est importé » :

if __name__ == "__main__":
main()

C’est le point d’entrée principal. Placez-le à la fin de chaque script. Un test ou un autre script peut alors importer le fichier pour utiliser ses fonctions, sans exécuter tout le programme.

Exécutez du Python dans votre navigateur

Que vaut __name__ ici?

Cet encadré exécute le code comme programme principal, comme le fait python file.py. Remplacez la condition par __name__ == "report", puis exécutez le code. Pourquoi main() ne s’exécute-t-elle plus?

Amira déplace les trois fonctions dans le paquet. Elle ne change pas le code à l’intérieur de chaque fonction.

ticket_cleaner/__init__.py
"""Clean support-ticket records and summarise them."""
ticket_cleaner/report.py
def filter_by_status(tickets, status="open"):
"""Return the tickets that have the given status."""
selected = []
for ticket in tickets:
if ticket["status"] == status:
selected.append(ticket)
return selected
def count_by_category(tickets):
"""Return the number of tickets in each category, sorted by category."""
counts = {}
for ticket in tickets:
category = ticket["category"]
counts[category] = counts.get(category, 0) + 1
return dict(sorted(counts.items()))
def average_priority(tickets):
"""Return the average priority, or None when there are no tickets."""
if not tickets:
return None
total = 0
for ticket in tickets:
total = total + ticket["priority"]
return round(total / len(tickets), 1)

Le script commence maintenant par une ligne d’importation et se termine par le point d’entrée principal :

count_tickets.py
from ticket_cleaner.report import average_priority, count_by_category, filter_by_status
TICKETS = [
{"id": "T-1001", "status": "open", "category": "billing", "priority": 2},
{"id": "T-1002", "status": "closed", "category": "login", "priority": 3},
{"id": "T-1003", "status": "open", "category": "login", "priority": 1},
{"id": "T-1004", "status": "pending", "category": "billing", "priority": 2},
{"id": "T-1005", "status": "open", "category": "billing", "priority": 3},
{"id": "T-1006", "status": "closed", "category": "shipping", "priority": 1},
]
def main():
status = "open"
selected = filter_by_status(TICKETS, status)
print(f"Tickets with status '{status}': {len(selected)}")
if not selected:
print("No tickets to count.")
return
for category, count in count_by_category(selected).items():
print(f" {category}: {count}")
print(f"Average priority: {average_priority(selected)}")
if __name__ == "__main__":
main()

La sortie reste la même. C’est le test d’un remaniement : la structure a changé, mais pas le comportement.

  1. Dans le dossier ticket-cleaner, créez le dossier du paquet :

    Fenêtre de terminal
    mkdir ticket_cleaner
  2. Dans VS Code, créez ticket_cleaner/__init__.py avec la seule ligne de chaîne de documentation de l’exemple détaillé.

  3. Créez ticket_cleaner/report.py. Déplacez-y les trois fonctions de count_tickets.py (coupez-les, puis collez-les).

  4. Dans count_tickets.py, ajoutez la ligne d’importation au début. Remplacez la dernière ligne, main(), par le point d’entrée principal.

  5. Exécutez le script :

    Fenêtre de terminal
    python count_tickets.py
    Sortie
    Tickets with status 'open': 3
    billing: 2
    login: 1
    Average priority: 2.0
  6. Affichez le contenu du dossier du paquet : ls ticket_cleaner. Vous voyez un nouveau dossier, __pycache__. Python y enregistre une copie compilée de chaque module importé pour accélérer la prochaine importation. Vous ne le modifiez pas. Si vous utilisez Git, ajoutez __pycache__/ à .gitignore.

La personne responsable du soutien veut un court rapport de tous les billets urgents, peu importe leur statut. Créez un nouveau script, urgent_report.py, dans le dossier du projet. Il doit :

  1. Importer filter_by_status du paquet. Ne copiez pas la fonction.
  2. Utiliser les six mêmes billets.
  3. Pour chaque statut ("open" (ouvert), "pending" (en attente), "closed" (fermé)), afficher le statut et le nombre de billets de priorité 1.
  4. Se terminer par le point d’entrée principal.
Expected output
open: 1 urgent
pending: 0 urgent
closed: 1 urgent
Afficher une réponse
urgent_report.py
from ticket_cleaner.report import filter_by_status
TICKETS = [
# the same six tickets as in count_tickets.py
]
def main():
for status in ["open", "pending", "closed"]:
selected = filter_by_status(TICKETS, status)
urgent = [ticket for ticket in selected if ticket["priority"] == 1]
print(f"{status}: {len(urgent)} urgent")
if __name__ == "__main__":
main()

Les six billets se trouvent maintenant dans deux scripts. C’est aussi une copie. Dans le module 4, les deux scripts liront plutôt les billets dans un seul fichier de données.

Erreur courante

Un nom de fichier qui masque un module Python

Symptôme : du code qui fonctionnait s’arrête avec une erreur concernant un module fourni avec Python. Par exemple, vous avez créé un fichier nommé csv.py pour vous exercer :

AttributeError: module 'csv' has no attribute 'DictReader' (consider renaming '/Users/amira/projects/ticket-cleaner/csv.py' since it has the same name as the standard library module named 'csv' and prevents importing that standard library module)

Cause : Python cherche d’abord les modules dans le dossier de votre script. Votre fichier csv.py porte le même nom que le module csv de Python. Donc, import csv trouve votre fichier à la place. Votre fichier ne contient pas DictReader. Les noms qui causent souvent ce problème sont csv.py, json.py, random.py, test.py, email.py et ceux des paquets installés, comme httpx.py.

Solution :

  1. Renommez votre fichier pour décrire votre code, par exemple read_tickets.py.
  2. Supprimez le dossier __pycache__ à côté du fichier pour qu’aucune copie compilée de l’ancien fichier ne reste.
  3. Exécutez le programme de nouveau.

Vérifiez votre compréhension

Vous importez un module, et le terminal affiche un rapport que vous n'avez pas demandé. Que manque-t-il dans ce module, et où?

Afficher la réponse

Le module appelle son code au niveau supérieur, donc l’importation l’exécute. Placez l’appel dans if __name__ == "__main__": à la fin du fichier. Le code s’exécute alors seulement quand vous lancez le fichier directement, pas quand un autre fichier l’importe.

Vérifiez votre compréhension

Pourquoi le paquet s'appelle-t-il ticket_cleaner et non ticket-cleaner, comme le dossier du projet?

Afficher la réponse

import ticket-cleaner n’est pas du Python valide, car Python interprète - comme un signe de soustraction. Le nom d’un module ou d’un paquet doit être un nom Python valide : lettres, chiffres et _, sans chiffre au début. Le dossier du projet n’est jamais importé, donc son nom peut contenir un -.

Récapitulatif

  • Un module est un fichier .py. Un paquet est un dossier de modules qui contient un fichier __init__.py.
  • import module et from module import name exécutent tous deux le module une fois. Python cherche d’abord dans le dossier du script, puis dans les modules Python, puis dans les paquets installés.
  • Ne donnez pas à vos fichiers les mêmes noms que les modules Python ou les paquets installés.
  • Terminez chaque script par if __name__ == "__main__": main() pour qu’une importation ne l’exécute pas.
  • Exécutez les scripts depuis le dossier du projet. Utilisez python -m package.module pour un module dans un paquet.

Ensuite, vous donnerez à chaque billet un type adapté avec une classe de données. Vous ajouterez aussi des annotations de type qui montrent le contrat de chaque fonction sur sa ligne def.

Leçon suivante : Les enregistrements structurés