Qu'est-ce que l'instruction import en Python ?
L'instruction import permet de charger du code situé dans d'autres fichiers (modules) ou d'autres dossiers (packages), afin de le réutiliser dans le module actuel.
C'est grâce à cette instruction que Python peut accéder à une multitude de classes et de fonctions, que ce soit via la bibliothèque standard, les bibliothèques/frameworks tiers ou bien même votre propre code réutilisable.
En Python, chaque fichier .py est un module. Pour utiliser les fonctions, classes et variables du module A dans le module B, il faut importer le module A ou, du moins, une partie du module A. Ce qui permet d'organiser son code.
Syntaxe
Il existe deux manières principales d'importer des éléments :
-
Importer le module complet
-
Importer un élément spécifique
Importer un module complet
On accède aux éléments via la notation « pointée ».
import itertools # On utilise le nom du module suivi d'un point donnees = [1, 2, 3] cycle_infini = itertools.cycle(donnees)
Importer un élément spécifique
Importer un élément spécifique permet d'utiliser directement cet élément sans le préfixer par le nom du module :
from itertools import islice # Pas besoin de mettre 'itertools.' devant resultat = list(islice(range(10), 5))
Éviter l'importation totale
L'importation avec l'astérisque * permet d'importer l'intégralité des objets d'un module dans le script actuel.
Attention
L'astérisque n'importe pas les fonctions ou variables commençant par un underscore (_). En Python, ces noms sont conventionnellement considérés comme privés (ou internes au module).
Cela peut paraître pratique au premier abord, mais la PEP 8 déconseille cette pratique. En effet, cette façon d'importer l'ensemble des éléments va polluer l'espace de noms.
En fait, cela pose plusieurs problèmes :
-
Des conflits de noms, car si deux modules différents possèdent une fonction portant le même nom, le second écrasera le premier sans que vous le sachiez jusqu'au moment de trouver le problème 😅
-
En lisant votre code, il est compliqué de savoir d'où provient une fonction spécifique, à moins de fouiller tous les modules importés (encore 😅)
Imaginons un module mon_module_de_calcul.py :
# mon_module_de_calcul.py def sqrt(x): return f"La racine carrée de {x} selon mon_module_de_calcul"
# main.py from math import * # math possède une fonction 'sqrt' from mon_module_de_calcul import * # Ce module possède aussi une fonction 'sqrt' # Laquelle est utilisée ici ? # Le débogage devient extrêmement difficile car la provenance de sqrt est floue. resultat = sqrt(25) print(resultat) # Affiche: La racine carrée de 25 selon mon_module_de_calcul
Utiliser des alias avec le mot-clé as
Il est possible de renommer un module ou une fonction lors de l'importation. C'est une pratique utile pour raccourcir des noms longs ou éviter un conflit de noms.
Prenons un exemple avec Django : imaginons une fonction (une vue dans le cas de Django) nommée login. Il n'est pas possible d'importer la fonction login de Django directement et de créer sa propre fonction login, car il y a un risque de conflit.
# On renomme la fonction de Django pour libérer le nom 'login' pour notre vue from django.contrib.auth import login as auth_login def login(request): """Ceci est notre vue personnalisée""" if request.method == "POST": # ... logique d'authentification ... # On utilise l'alias pour appeler la fonction originale de Django auth_login(request, user)
Gestion des chemins : imports absolus et relatifs
Commençons par parler des imports absolus : c'est la méthode recommandée par la PEP 8. Avec cette méthode, il faut spécifier le chemin depuis la racine du projet :
# Structure du projet : # mon_application/ # traitement/ # analyse.py # donnees/ # base.py # Dans traitement/analyse.py (Import absolu) : from donnees.base import charger_donnees
Contrairement aux imports absolus, les imports relatifs utilisent le point pour naviguer dans l'arborescence depuis le fichier actuel :
-
.désigne le dossier où se trouve le fichier actuel -
..désigne le dossier parent
Prenons un exemple :
- Arborescence du projet
lecteur_audio/ ├── core/ │ ├── __init__.py │ ├── moteur.py <-- Nous sommes ici │ └── décodeur.py └── utils/ ├── __init__.py <-- Fichier d'initialisation du package utils └── formateur.py <-- Contient la fonction 'formater_duree'
moteur.py
# Import relatif dans le même dossier (un seul point) # On cible le fichier 'décodeur.py' situé à côté from .décodeur import lire_mp3 # Import relatif en remontant au parent pour aller dans un autre dossier # '..' remonte à 'lecteur_audio/', puis on descend dans 'utils/formateur.py' from ..utils.formateur import formater_duree
À quoi sert le fichier __init__.py pour les imports ?
Le fichier __init__.py permet d'exposer des objets au niveau du package. Il sert de façade en cachant la structure interne des fichiers.
Reprenons l'exemple précédent en modifiant le fichier utils/__init__.py :
# utils/__init__.py from .formateur import formater_duree
La fonction formater_duree est maintenant disponible au niveau du dossier utils. Reprenons le contenu de moteur.py :
# moteur.py # Maintenant : from ..utils import formater_duree # Avant il fallait connaître le nom du fichier interne 'formateur' from ..utils.formateur import formater_duree
La variable __all__
Pour contrôler ce qui doit être importé lorsqu'un utilisateur fait from mon_module import *, il est possible et même recommandé d'utiliser la variable __all__.
Par défaut, Python importe tout (sauf les objets privés). Mais en définissant __all__, vous définissez une liste d'objets qu'il sera possible d'importer dans les autres modules.
Attention
__all__ ne sert pas que pour les packages. Il est possible de l'utiliser dans n'importe quel module Python.
Prenons un exemple de système d'une fusée (soyons fous). Le pilote (qui s'appelle Patrick) doit pouvoir décoller, mais on ne veut absolument pas qu'il déclenche l'autodestruction :
# Fichier: fusee.py def decoller(): return "3, 2, 1... Décollage !" def autodestruction(): return "BOUM ! La fusée a explosé." # Seule la fonction 'decoller' est sécurisée pour un export par défaut __all__ = ["decoller"]
Dans un autre fichier :
from fusee import * print(decoller()) # Affiche : "3, 2, 1... Décollage !" print(autodestruction()) # NameError : Ouf ! Cette fonction dangereuse n'a pas été importée.
Pour les curieux : le fonctionnement interne
L'instruction import appelle la fonction interne __import__ :
import itertools # Ce que Python fait en arrière plan : itertools = __import__("itertools")
itertools = __import__('itertools') liste = ['a', 'b', 'c'] combinaisons = list(itertools.combinations(liste, 2)) print("Combinaisons de 2 éléments :", combinaisons) # Combinaisons de 2 éléments : [('a', 'b'), ('a', 'c'), ('b', 'c')]
Attention
Cette fonction __import__ existe, mais il est déconseillé de l'utiliser directement.
Les imports et la PEP 8
Nous avons déjà parlé de quelques bonnes pratiques, mais voici d'autres conventions qu'il est conseillé de respecter :
-
Placez tous les imports au tout début d'un fichier
-
Ordre de tri :
-
Bibliothèques standards
-
Bibliothèques tierces
-
Modules locaux
-