25. Import Système Avancé
importlib, import hooks, namespace packages, lazy loading,sys.meta_path,__path__,finders/loaders,pkgutil.
25.1 Rappel : comment fonctionne import
import mon_module- Cherche dans
sys.modules(cache) - Si absent, parcourt
sys.meta_path(finders) - Le finder trouve le loader, qui exécute le module
- Stocke dans
sys.modules
import sys
sys.modules["math"] # <module 'math' ...> (déjà en cache)25.2 sys.meta_path — les importateurs
import sys
for finder in sys.meta_path:
print(finder)
# <class '_frozen_importlib.BuiltinImporter'>
# <class '_frozen_importlib.FrozenImporter'>
# <class '_frozen_importlib_external.PathFinder'>- BuiltinImporter : modules compilés en C (ex:
sys,math) - FrozenImporter : modules gelés dans l’exécutable
- PathFinder : parcourt
sys.pathpour les fichiers.py/.so
25.3 Import dynamique avec importlib
import importlib
# Import par nom (chaîne)
math = importlib.import_module("math")
math.sqrt(16) # 4.0
# Import de sous-module
os_path = importlib.import_module("os.path")Équivalent à :
import math # connu à la compilationAvantage : le nom du module peut être une variable.
nom_module = "numpy"
np = importlib.import_module(nom_module)importlib.util.spec_from_file_location
Importer un fichier spécifique :
import importlib.util
spec = importlib.util.spec_from_file_location("mon_module", "/chemin/vers/module.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
module.ma_fonction()25.4 __import__ — la fonction native
# Niveau le plus bas
math = __import__("math")
# Équivaut à import math
# Import hiérarchique
os = __import__("os.path")
# os = sys.modules["os"] (pas os.path)Déconseillé : préférer importlib.import_module.
25.5 Finders et Loaders personnalisés
Charger des modules depuis une source non standard (base de données, réseau, etc.) :
import sys
import importlib.abc
import importlib.machinery
class DatabaseFinder(importlib.abc.MetaPathFinder):
"""Trouve des modules stockés en base de données."""
def find_spec(self, fullname, path, target=None):
if not fullname.startswith("db_"):
return None
return importlib.machinery.ModuleSpec(
fullname,
DatabaseLoader(fullname),
)
class DatabaseLoader(importlib.abc.Loader):
def __init__(self, fullname):
self.fullname = fullname
def create_module(self, spec):
return None # utilisation du mécanisme par défaut
def exec_module(self, module):
# Charger le code depuis la base de données
code = """
def hello():
print("Bonjour depuis la DB !")
"""
exec(code, module.__dict__)
module.__loader__ = self
# Enregistrer le finder AVANT les imports
sys.meta_path.insert(0, DatabaseFinder())
import db_hello
db_hello.hello() # "Bonjour depuis la DB !"25.6 Path hooks — sys.path_hooks
Pour des protocoles personnalisés :
# Exemple: importer depuis une archive zip
import zipimport
import sys
# zipimport est déjà dans sys.path_hooks par défaut
# On peut ajouter un zip directement dans sys.path
sys.path.append("/chemin/vers/archive.zip")
# Les modules dans l'archive sont importablesCréer un path hook personnalisé
import sys
import importlib.abc
class MyPathFinder(importlib.abc.PathEntryFinder):
def __init__(self, path):
self.path = path
def find_spec(self, fullname, target=None):
...
def path_hook(path):
if path.startswith("custom://"):
return MyPathFinder(path)
raise ImportError
sys.path_hooks.append(path_hook)25.7 Reload — recharger un module
import importlib
import mon_module
# Modifier mon_module...
importlib.reload(mon_module) # rechargerAttention : reload ne met pas à jour les références existantes (from mon_module import f reste l’ancienne fonction).
25.8 Namespace packages
Paquets sans __init__.py, qui peuvent être répartis sur plusieurs dossiers :
projet/
├── paquet/
│ └── module_a.py
autre_chemin/
├── paquet/
│ └── module_b.py
import sys
sys.path.extend(["projet", "autre_chemin"])
import paquet.module_a # de projet/paquet/
import paquet.module_b # de autre_chemin/paquet/Création :
# pas de __init__.py dans les dossiers paquet/
# Python 3.3+ les détecte automatiquement comme namespace packagesCas d’usage : plugins, extensions, séparation de codebase.
25.9 Lazy loading
Importer seulement quand nécessaire :
import importlib.util
import sys
class LazyLoader:
def __init__(self, module_name):
self.module_name = module_name
self._module = None
def __getattr__(self, name):
if self._module is None:
self._module = importlib.import_module(self.module_name)
return getattr(self._module, name)
numpy = LazyLoader("numpy")
# numpy n'est importé que lors du premier accès
numpy.array([1, 2, 3]) # import iciApproche standard : importlib.util.LazyLoader (stdlib, 3.5+)
import importlib.util
import importlib.abc
spec = importlib.util.spec_from_loader("lazy_math", None)
lazy_math = importlib.util.module_from_spec(spec)
spec.loader = importlib.util.LazyLoader(spec.loader)
sys.modules["lazy_math"] = lazy_math25.10 pkgutil — exploration de paquets
import pkgutil
# Lister les sous-modules d'un paquet
modules = list(pkgutil.iter_modules(["chemin/vers/paquet"]))
for finder, name, ispkg in modules:
print(name, ispkg)
# Importer tous les sous-modules
for finder, name, ispkg in pkgutil.walk_packages(["paquet"]):
importlib.import_module(f"paquet.{name}")Utile pour les plugins auto-découverts :
# plugins/__init__.py
import importlib
import pkgutil
def charger_plugins():
plugins = {}
for finder, name, ispkg in pkgutil.iter_modules(__path__):
module = importlib.import_module(f"plugins.{name}")
if hasattr(module, "Plugin"):
plugins[name] = module.Plugin
return plugins25.11 sys.modules — manipulation directe
import sys
# Ajouter un module factice
class FauxModule:
def fonction(self):
return "factice"
sys.modules["faux"] = FauxModule()
import faux
faux.fonction() # "factice"# Retirer un module (forcer le réimport)
if "mon_module" in sys.modules:
del sys.modules["mon_module"]
import mon_module # réimporté25.12 __init__.py avancé
# Dans mon_paquet/__init__.py
from .module_a import ClasseImportante
from .sous_paquet import fonction_utile
__all__ = ["ClasseImportante", "fonction_utile"]
# Initialisation à l'import du paquet
print("Paquet initialisé")__path__ :
# Dans __init__.py
print(__path__) # list[str] des chemins du paquet
__path__.append("/autre/chemin") # ajouter des sous-modules dynamiquement25.13 Bonnes pratiques
importlib.import_moduleplutôt que__import__reloadutile en développement interactif (IPython%autoreload)- Namespace packages : uniquement quand nécessaire (plugins distribués)
- Lazy loading : utile pour les imports coûteux conditionnels
- Ne pas abuser des finders personnalisés