Skip to main content
Niveau : Intermédiaire

Chapitre 21 : Modules et Packages

import, from, __init__.py, __name__

Objectif

Ce module a pour but de vous apprendre à structurer votre code Python en modules et packages. Vous découvrirez comment importer du code depuis d'autres fichiers, comment organiser un projet en plusieurs dossiers, et les bonnes pratiques pour créer des applications maintenables.

1. Qu'est-ce qu'un Module ?

Un module est tout simplement un fichier Python (.py). L'intérêt des modules est de pouvoir diviser un programme complexe en plusieurs fichiers logiques, ce qui améliore l'organisation et la réutilisabilité du code.

Chaque module a sa propre portée globale. Les variables, fonctions et classes définies dans un module ne sont pas directement accessibles depuis un autre. Pour les utiliser, il faut les importer.

Exemple

Imaginons la structure de fichiers suivante :

projet/
├── main.py
└── mon_module.py

Fichier mon_module.py :

print("Le module 'mon_module' est en cours d'importation.")

PI = 3.14159

def saluer(nom):
return f"Bonjour, {nom} !"

class MaClasse:
def __init__(self):
print("Instance de MaClasse créée.")

Fichier main.py :

# Importe le module entier
import mon_module

print(f"La valeur de PI est {mon_module.PI}")

message = mon_module.saluer("Monde")
print(message)

instance = mon_module.MaClasse()

Exécution de main.py :

Le module 'mon_module' est en cours d'importation.
La valeur de PI est 3.14159
Bonjour, Monde !
Instance de MaClasse créée.

Le code dans mon_module.py est exécuté une seule fois, lors du premier import.

2. Les Différentes Syntaxes d'Importation

a. import <module>

Importe le module entier. Pour accéder à ses membres, on doit utiliser la notation module.membre. C'est la méthode la plus claire et la plus recommandée car elle évite les conflits de noms.

import math
print(math.sqrt(16))

b. from <module> import <membre>

Importe un membre spécifique (fonction, classe, variable) directement dans la portée actuelle.

from math import sqrt, pi

# On peut maintenant utiliser sqrt et pi directement
print(sqrt(16))
print(pi)

c. from <module> import <membre> as <alias>

Importe un membre et lui donne un alias (un autre nom). C'est utile pour éviter les conflits de noms ou pour raccourcir un nom long.

from math import sqrt as racine_carree
print(racine_carree(16))

On peut aussi donner un alias à un module entier :

import mon_module as mm
print(mm.PI)

d. from <module> import * (À ÉVITER)

Importe tous les noms publics d'un module dans la portée actuelle. Cette pratique est fortement déconseillée car :

  • Elle pollue la portée actuelle avec des noms potentiellement inconnus.
  • Elle rend le code très difficile à lire (on ne sait pas d'où vient une fonction).
  • Elle peut masquer des variables ou fonctions existantes.

3. Qu'est-ce qu'un Package ?

Lorsque votre projet grandit, vous pouvez avoir besoin de plus de structure qu'une simple liste de fichiers. Un package est un dossier qui contient d'autres modules (et potentiellement d'autres packages).

Pour que Python reconnaisse un dossier comme un package, il doit contenir un fichier spécial nommé __init__.py. Ce fichier peut être vide.

Exemple de structure de package

shop/
├── __init__.py
├── main.py
├── products/
│ ├── __init__.py
│ └── database.py
└── users/
├── __init__.py
└── auth.py

Dans cet exemple, shop, products et users sont des packages.

Importer depuis des packages

Pour importer le module database depuis main.py, on utilise la notation avec des points :

Fichier main.py :

# Importation absolue
from products.database import find_product
from users.auth import login

produit = find_product(101)
user = login("test", "password")

Le fichier __init__.py

Ce fichier a deux rôles principaux :

  1. Marquer un dossier comme un package Python.
  2. Exécuter du code d'initialisation pour le package. On peut y définir des variables pour le package ou importer automatiquement certains modules.

Par exemple, pour simplifier les imports, on peut modifier products/__init__.py :

Fichier products/__init__.py :

# Rend la fonction find_product directement accessible depuis le package products
from .database import find_product

Maintenant, depuis main.py, l'import devient plus court :

# L'import est plus simple grâce à __init__.py
from products import find_product

4. Imports Absolus vs. Relatifs

  • Import absolu : Spécifie le chemin depuis la racine du projet. C'est la méthode la plus claire et la plus recommandée.

    from products.database import find_product
  • Import relatif : Spécifie le chemin par rapport au module actuel. Utile à l'intérieur d'un package pour se référer à des modules frères.

    • . : Le package actuel.
    • .. : Le package parent.

    Imaginons que users/auth.py ait besoin d'une fonction de products/database.py. Ce n'est pas une bonne conception, mais pour l'exemple :

    # Dans users/auth.py
    from ..products.database import find_product # Remonte d'un niveau (..) puis descend dans products

    Les imports relatifs peuvent rendre la structure du projet plus difficile à comprendre et sont à utiliser avec parcimonie.

5. Le Piège du if __name__ == '__main__':

Quand un fichier Python est exécuté directement, Python lui assigne la valeur spéciale "__main__" à sa variable globale __name__. Quand un fichier est importé comme un module, __name__ prend la valeur du nom du module (le nom du fichier).

Cela permet d'écrire du code qui ne s'exécute que lorsque le fichier est le script principal.

Fichier mon_module.py :

def ma_fonction():
print("Fonction du module.")

print(f"La valeur de __name__ dans mon_module.py est : {__name__}")

# Ce bloc ne s'exécutera que si on lance 'python mon_module.py'
if __name__ == '__main__':
print("Ce code s'exécute car le module est le script principal.")
ma_fonction()

Fichier main.py :

import mon_module

print("Appel depuis main.py")
mon_module.ma_fonction()

Exécution de main.py :

La valeur de __name__ dans mon_module.py est : mon_module
Appel depuis main.py
Fonction du module.

Le code dans le if de mon_module.py n'est pas exécuté. C'est l'endroit idéal pour mettre des tests, des exemples d'utilisation ou le point de départ d'une application.

Conclusion

Les modules et les packages sont les outils fondamentaux pour organiser des projets Python de toute taille. En divisant votre code en unités logiques, vous améliorez sa lisibilité, sa maintenabilité et sa réutilisabilité. La maîtrise des différentes syntaxes d'import et du rôle du fichier __init__.py est essentielle pour construire des applications robustes et bien structurées.

Exercices :

Exercice 21 - Création et Utilisation d'un Package Simple

Objectif

Cet exercice a pour but de vous faire créer une structure de package simple, d'y définir des modules avec des fonctions, et de les utiliser depuis un script principal. Cela vous permettra de mettre en pratique la création de packages, les imports absolus et le rôle du fichier __init__.py.

Contexte

Vous allez créer un petit package de "calculs" qui sera structuré en deux sous-packages : operations pour les calculs arithmétiques de base et scientifique pour des calculs plus avancés.

Énoncé

Étape 1 : Créer la structure du projet

Créez l'arborescence de fichiers et de dossiers suivante :

mon_projet/
├── main.py
└── calculs/
├── __init__.py
├── operations/
│ ├── __init__.py
│ └── arithmetique.py
└── scientifique/
├── __init__.py
└── stats.py

Tous les fichiers __init__.py peuvent être vides au début.

Étape 2 : Remplir les modules

  1. Dans calculs/operations/arithmetique.py : Définissez deux fonctions simples :

    • addition(a, b) qui retourne a + b.
    • soustraction(a, b) qui retourne a - b.
  2. Dans calculs/scientifique/stats.py : Définissez une fonction :

    • moyenne(nombres) qui prend une liste de nombres et retourne leur moyenne.

Étape 3 : Utiliser les modules dans main.py

Ouvrez main.py et, en utilisant des imports absolus, importez et utilisez les fonctions que vous venez de créer.

# Dans main.py

# Importer les fonctions nécessaires
from calculs.operations.arithmetique import addition, soustraction
from calculs.scientifique.stats import moyenne

# Utiliser les fonctions
somme = addition(10, 5)
difference = soustraction(10, 5)
moy = moyenne([10, 20, 30, 40])

print(f"10 + 5 = {somme}")
print(f"10 - 5 = {difference}")
print(f"La moyenne est : {moy}")

À ce stade, exécutez main.py et assurez-vous que tout fonctionne correctement.

Étape 4 : Simplifier les imports avec __init__.py

Votre objectif est de rendre les imports plus courts et plus pratiques. Vous voulez pouvoir importer les fonctions directement depuis calculs.operations et calculs.scientifique.

  1. Modifiez calculs/operations/__init__.py :

    • Ajoutez la ligne suivante pour "promouvoir" les fonctions du module arithmetique au niveau du package operations :
    from .arithmetique import addition, soustraction

    (Le . indique un import relatif depuis le même package).

  2. Modifiez calculs/scientifique/__init__.py :

    • Faites de même pour la fonction moyenne :
    from .stats import moyenne

Étape 5 : Mettre à jour main.py

Maintenant que les __init__.py sont configurés, modifiez main.py pour utiliser les imports simplifiés.

# Dans main.py (version mise à jour)

# Les imports sont maintenant plus courts
from calculs.operations import addition, soustraction
from calculs.scientifique import moyenne

# Le reste du code ne change pas
somme = addition(10, 5)
difference = soustraction(10, 5)
moy = moyenne([10, 20, 30, 40])

print(f"10 + 5 = {somme}")
print(f"10 - 5 = {difference}")
print(f"La moyenne est : {moy}")
Cliquez ici pour voir un exemple de code de solution

(Voir le code dans l'énoncé de l'étape 5)