100 votes

Importer un fichier source Python arbitraire. (Python 3.3+)

Comment puis-je importer un fichier source Python arbitraire (dont le nom de fichier peut contenir n'importe quel caractère et ne se termine pas toujours par .py) en Python 3.3+ ?

J'ai utilisé imp.load_module comme suit :

>>> import imp
>>> path = '/tmp/a-b.txt'
>>> with open(path, 'U') as f:
...     mod = imp.load_module('a_b', f, path, ('.py', 'U', imp.PY_SOURCE))
...
>>> mod

Cela fonctionne toujours en Python 3.3, mais selon la documentation de imp.load_module, elle est obsolète :

Obsolète depuis la version 3.3 : Inutile car les chargeurs doivent être utilisés pour charger les modules et find_module() est obsolète.

et la documentation du module imp recommande d'utiliser importlib :

Note : Les nouveaux programmes doivent utiliser importlib plutôt que ce module.

Quelle est la bonne manière de charger un fichier source Python arbitraire en Python 3.3+ sans utiliser la fonction obsolète imp.load_module ?

119voto

falsetru Points 109148

Une solution a été trouvée à partir du code de test importlib.

En utilisant importlib.machinery.SourceFileLoader:

>>> import importlib.machinery
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> mod = loader.load_module()
>>> mod

REMARQUE : fonctionne uniquement avec Python 3.3+.

MISE À JOUR Loader.load_module est obsolète depuis Python 3.4. Utilisez Loader.exec_module à la place :

>>> import types
>>> import importlib.machinery
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> mod = types.ModuleType(loader.name)
>>> loader.exec_module(mod)
>>> mod

>>> import importlib.machinery
>>> import importlib.util
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> spec = importlib.util.spec_from_loader(loader.name, loader)
>>> mod = importlib.util.module_from_spec(spec)
>>> loader.exec_module(mod)
>>> mod

48voto

Stefan Scherfke Points 1508

Mis à jour pour Python >= 3.8:

Version courte :

>>> # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
>>> import importlib.util, sys
>>> spec = importlib.util.spec_from_file_location(modname, fname)
>>> module = importlib.util.module_from_spec(spec)
>>> sys.modules[modname] = module
>>> spec.loader.exec_module(module)

Version complète :

>>> import importlib.util
>>> import sys
>>> from pathlib import Path
>>> from typing import TYPE_CHECKING
>>> 
>>> 
>>> if TYPE_CHECKING:
...     import types
...
...
>>> def import_source_file(fname: str | Path, modname: str) -> "types.ModuleType":
...     """
...     Importer un fichier source Python et renvoyer le module chargé.

...     Args:
...         fname: Le chemin complet du fichier source. Il peut contenir des caractères comme `.`
...             ou `-`.
...         modname: Le nom du module chargé. Il peut contenir des `.` et même des caractères
...             qui ne seraient normalement pas autorisés (par exemple, `-`).
...     Return:
...         Le module importé

...     Raises:
...         ImportError: Si le fichier ne peut pas être importé (par exemple, s'il ne s'agit pas d'un fichier `.py` ou s'il
...             n'existe pas).
...         Exception: Toute exception levée lors de l'exécution du module (par exemple,
...             :exc:`SyntaxError`). Ce sont des erreurs commises par l'auteur du module!
...     """
...     # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
...     spec = importlib.util.spec_from_file_location(modname, fname)
...     if spec est None:
...         raise ImportError(f"Impossible de charger la spécification pour le module '{modname}' à : {fname}")
...     module = importlib.util.module_from_spec(spec)
...     sys.modules[modname] = module
...     try:
...         spec.loader.exec_module(module)
...     except FileNotFoundError as e:
...         raise ImportError(f"{e.strerror} : {fname}") from e
...     return module
...
>>> import_source_file(Path("/tmp/my_mod.py"), "my_mod")

Réponse originale pour Python 3.5 et 3.6

Version plus courte de la solution de @falsetru :

>>> import importlib.util
>>> spec = importlib.util.spec_from_file_location('a_b', '/tmp/a-b.py')
>>> mod = importlib.util.module_from_spec(spec)
>>> spec.loader.exec_module(mod)
>>> mod

Je l'ai testé avec Python 3.5 et 3.6.

Conformément aux commentaires, cela ne fonctionne pas avec des extensions de fichier arbitraires.

16voto

awalllllll Points 388

Similaire à @falsetru mais pour Python 3.5+ et tenant compte de ce que dit la documentation de importlib sur l'utilisation de importlib.util.module_from_spec au lieu de types.ModuleType:

Cette fonction [importlib.util.module_from_spec] est préférée à l'utilisation de types.ModuleType pour créer un nouveau module car spec est utilisé pour définir autant d'attributs contrôlés par l'importation sur le module que possible.

Nous pouvons importer n'importe quel fichier avec importlib seul en modifiant la liste importlib.machinery.SOURCE_SUFFIXES.

import importlib

importlib.machinery.SOURCE_SUFFIXES.append('') # chaîne vide pour autoriser n'importe quel fichier
spec = importlib.util.spec_from_file_location(nom_module, chemin_fichier)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# si désiré : importlib.machinery.SOURCE_SUFFIXES.pop()

6voto

Ciro Santilli Points 3341

Fonction d'aide importlib

Voici un helper pratique et prêt à l'emploi pour remplacer imp, avec un exemple. La technique est la même que celle de https://stackoverflow.com/a/19011259/895245, ceci fournit simplement une fonction plus pratique.

main.py

#!/usr/bin/env python3

import os
import importlib

def import_path(path):
    module_name = os.path.basename(path).replace('-', '_')
    spec = importlib.util.spec_from_loader(
        module_name,
        importlib.machinery.SourceFileLoader(module_name, path)
    )
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    sys.modules[module_name] = module
    return module

nonprincipal = import_path('non-principal')
print(nonprincipal)
print(nonprincipal.x)

non-principal

x = 1

Exécuter:

python3 main.py

Résultat:

1

Je remplace - par _ parce que mes exécutables Python importables sans extension ont des tirets comme dans mon-cmd. Ce n'est pas obligatoire, mais produit de meilleurs noms de module comme mon_cmd.

Ce motif est également mentionné dans la documentation à l'adresse : https://docs.python.org/3.7/library/importlib.html#importing-a-source-file-directly

J'ai fini par passer à cela car après la mise à jour vers Python 3.7, import imp affiche :

Avertissement de dépréciation : le module imp est obsolète en faveur de importlib ; voir la documentation du module pour des utilisations alternatives

et je ne sais pas comment l'arrêter, cela a été demandé à :

Lié:

Testé en Python 3.7.3.

1voto

abdo Points 21

Après de nombreuses solutions échouées, celle-ci fonctionne pour moi

def _import(func,*args):
    import os
    from importlib import util
    module_name = "mon_module"
    BASE_DIR = "chemin du répertoire du module souhaité"
    path =  os.path.join(BASE_DIR,module_name)
    spec = util.spec_from_file_location(func, path)
    mod = util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return getattr(mod,func)(*args)

et pour l'appeler, il suffit d'écrire le nom de la fonction et ses paramètres _import("fonction",*args)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X