Comment importer un module avec son nom sous forme de chaîne?

543

J'écris une application Python qui prend comme commande un argument, par exemple:

$ python myapp.py command1

Je veux que l'application soit extensible, c'est-à-dire qu'elle puisse ajouter de nouveaux modules qui implémentent de nouvelles commandes sans avoir à changer la source principale de l'application. L'arbre ressemble à quelque chose comme:

myapp/
    __init__.py
    commands/
        __init__.py
        command1.py
        command2.py
    foo.py
    bar.py

Je souhaite donc que l'application trouve les modules de commande disponibles au moment de l'exécution et exécute le module approprié.

Python définit une fonction __import__ , qui prend une chaîne pour un nom de module:

__import __ (nom, globaux = Aucun, locaux = Aucun, fromlist = (), niveau = 0)

La fonction importe le nom du module, en utilisant potentiellement les valeurs globales et locales données pour déterminer comment interpréter le nom dans un contexte de package. La fromlist donne les noms des objets ou sous-modules qui doivent être importés du module donné par nom.

Source: https://docs.python.org/3/library/functions.html# import

Donc, actuellement, j'ai quelque chose comme:

command = sys.argv[1]
try:
    command_module = __import__("myapp.commands.%s" % command, fromlist=["myapp.commands"])
except ImportError:
    # Display error message

command_module.run()

Cela fonctionne très bien, je me demande simplement s'il existe un moyen plus idiomatique d'accomplir ce que nous faisons avec ce code.

Notez que je ne veux pas spécifiquement utiliser des œufs ou des points d'extension. Ce n'est pas un projet open-source et je ne m'attends pas à ce qu'il y ait des "plugins". Le but est de simplifier le code de l'application principale et de supprimer la nécessité de le modifier chaque fois qu'un nouveau module de commande est ajouté.

Kamil Kisiel
la source
Que fait fromlist = ["myapp.commands"]?
Pieter Müller
@ PieterMüller: dans un type shell python ceci: dir(__import__). La fromlist doit être une liste de noms à émuler "from import name ...".
mawimawi
À partir de 2019, vous devriez rechercher importlib: stackoverflow.com/a/54956419/687896
Brandt
N'utilisez pas __import__ voir Python Doc utilisez un importlib.import_module
fcm

Réponses:

321

Avec Python plus ancien que 2.7 / 3.1, c'est à peu près ainsi que vous le faites.

Pour les versions plus récentes, voir importlib.import_modulepour Python 2 et et Python 3 .

Vous pouvez également l'utiliser execsi vous le souhaitez.

Ou en utilisant, __import__vous pouvez importer une liste de modules en procédant comme suit:

>>> moduleNames = ['sys', 'os', 're', 'unittest'] 
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)

Extrait directement de Dive Into Python .

Harley Holcombe
la source
7
quelle est la différence avec l'exec?
user1767754
1
Comment pourriez-vous utiliser __init__ce module?
Dorian Dore
7
Un problème avec cette solution pour l'OP est que le recouvrement des exceptions pour un ou deux modules de "commande" incorrects fait échouer son application de commande entière sur une exception. Personnellement, je ferais une boucle sur chaque importation individuellement enveloppée dans un essai: mods = __ import __ () \ nexcept ImportError en tant qu'erreur: rapport (erreur) pour permettre aux autres commandes de continuer à fonctionner pendant que les mauvaises sont corrigées.
DevPlayer
7
Un autre problème avec cette solution, comme le souligne Denis Malinovsky ci-dessous, est que les docs python eux-mêmes recommandent de ne pas les utiliser __import__. Les documents 2.7: "Parce que cette fonction est destinée à être utilisée par l'interpréteur Python et non à un usage général, il est préférable d'utiliser importlib.import_module () ..." Lors de l'utilisation de python3, le impmodule résout ce problème, comme le mentionne Monkut ci-dessous.
LiavK
2
@JohnWu pourquoi avez-vous besoin foreachquand vous en avez for element inet map()bien :)
cowbert
300

La méthode recommandée pour Python 2.7 et 3.1 et versions ultérieures consiste à utiliser le importlibmodule:

importlib.import_module (nom, package = Aucun)

Importez un module. L'argument nom spécifie le module à importer en termes absolus ou relatifs (par exemple, pkg.mod ou ..mod). Si le nom est spécifié en termes relatifs, l'argument package doit être défini sur le nom du package qui doit servir d'ancrage pour résoudre le nom du package (par exemple, import_module ('.. mod', 'pkg.subpkg') importera pkg.mod).

par exemple

my_module = importlib.import_module('os.path')
Denis Malinovsky
la source
2
Recommandé par quelle source ou autorité?
michuelnik
66
Documentation conseille contre l' utilisation __import__fonction en faveur du module mentionné ci - dessus.
Denis Malinovsky
8
Cela fonctionne bien pour l'importation os.path; que diriez-vous from os.path import *?
Nam G VU
5
J'obtiens la réponse ici stackoverflow.com/a/44492879/248616 ie. appelglobals().update(my_module.__dict)
Nam G VU
2
@NamGVU, c'est une méthode dangereuse car elle pollue vos globaux et peut remplacer tous les identifiants avec les mêmes noms. Le lien que vous avez publié contient une version améliorée et améliorée de ce code.
Denis Malinovsky
132

Remarque: imp est déconseillé depuis Python 3.4 en faveur de importlib

Comme mentionné, le module imp vous offre des fonctions de chargement:

imp.load_source(name, path)
imp.load_compiled(name, path)

Je les ai déjà utilisés pour effectuer quelque chose de similaire.

Dans mon cas, j'ai défini une classe spécifique avec des méthodes définies qui étaient nécessaires. Une fois que j'ai chargé le module, je vérifierais si la classe était dans le module, puis je créerais une instance de cette classe, quelque chose comme ceci:

import imp
import os

def load_from_file(filepath):
    class_inst = None
    expected_class = 'MyClass'

    mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1])

    if file_ext.lower() == '.py':
        py_mod = imp.load_source(mod_name, filepath)

    elif file_ext.lower() == '.pyc':
        py_mod = imp.load_compiled(mod_name, filepath)

    if hasattr(py_mod, expected_class):
        class_inst = getattr(py_mod, expected_class)()

    return class_inst
monkut
la source
2
Bonne et simple solution. J'en ai écrit un semblable: stamat.wordpress.com/dynamic-module-import-in-python Mais le vôtre a quelques défauts: qu'en est-il des exceptions? IOError et ImportError? Pourquoi ne pas vérifier d'abord la version compilée, puis la version source. Attendre une classe réduit la réutilisabilité dans votre cas.
stamat
3
Dans la ligne où vous construisez MyClass dans le module cible, vous ajoutez une référence redondante au nom de la classe. Il est déjà stocké dans expected_classdonc vous pouvez le faire à la class_inst = getattr(py_mod,expected_name)()place.
Andrew
1
Notez que s'il existe un fichier compilé correctement en octets (avec le suffixe .pyc ou .pyo), il sera utilisé au lieu d'analyser le fichier source donné . https://docs.python.org/2/library/imp.html#imp.load_source
cdosborn
1
Attention: cette solution fonctionne; cependant, le module imp va être déprécié en faveur de l'import lib, voir la page imp: "" "Déconseillé depuis la version 3.4: Le paquet imp est en attente de dépréciation en faveur de l'importlib." ""
Ricardo
15

De nos jours, vous devriez utiliser importlib .

Importer un fichier source

Les documents fournissent en fait une recette pour cela, et cela se passe comme suit:

import sys
import importlib.util

file_path = 'pluginX.py'
module_name = 'pluginX'

spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

# check if it's all there..
def bla(mod):
    print(dir(mod))
bla(module)

De cette façon, vous pouvez accéder aux membres (par exemple, une fonction " hello") de votre module pluginX.py- dans cet extrait appelé module- sous son espace de noms; Par exemple, module.hello().

Si vous souhaitez importer les membres (par exemple, " hello"), vous pouvez inclure module/ pluginXdans la liste des modules en mémoire:

sys.modules[module_name] = module

from pluginX import hello
hello()

Importer un package

L'importation d'un package ( par exemple , pluginX/__init__.py) sous votre répertoire actuel est en fait simple:

import importlib

pkg = importlib.import_module('pluginX')

# check if it's all there..
def bla(mod):
    print(dir(mod))
bla(pkg)
Brandt
la source
3
ajouter sys.modules[module_name] = moduleà la fin de votre code si vous voulez pouvoir import module_nameensuite
Daniel Braun
@DanielBraun faisant ainsi, obtenez-moi une erreur> ModuleNotFoundError
Nam G VU
10

Si vous le souhaitez dans vos locaux:

>>> mod = 'sys'
>>> locals()['my_module'] = __import__(mod)
>>> my_module.version
'2.6.6 (r266:84297, Aug 24 2010, 18:46:32) [MSC v.1500 32 bit (Intel)]'

même fonctionnerait avec globals()

Jonathan
la source
2
La documentation de la locals()fonction intégrée avertit explicitement que "le contenu de ce dictionnaire ne doit pas être modifié".
martineau
9

Vous pouvez utiliser exec:

exec("import myapp.commands.%s" % command)
Greg Hewgill
la source
Comment puis-je obtenir un descripteur du module via l'exec, afin de pouvoir appeler (dans cet exemple) la méthode .run ()?
Kamil Kisiel
5
Vous pouvez ensuite faire getattr (myapp.commands, commande) pour accéder au module.
Greg Hewgill
1
... ou ajouter as command_moduleà la fin de la déclaration d'importation, puis fairecommand_module.run()
oxfn
Dans une version de Python 3+, exec a été converti en fonction avec l'avantage supplémentaire que la résultante référencée créée dans la source soit stockée dans l'argument locals () de exec (). Pour isoler davantage l'exec référencé des sections locales du bloc de code local, vous pouvez fournir votre propre dict, comme un vide et référencer les références en utilisant ce dict, ou passer ce dict à d'autres fonctions exec (source, gobals (), command1_dict) .... print (command1_dict ['somevarible'])
DevPlayer
2

Similaire à la solution de @monkut mais réutilisable et tolérante aux erreurs décrite ici http://stamat.wordpress.com/dynamic-module-import-in-python/ :

import os
import imp

def importFromURI(uri, absl):
    mod = None
    if not absl:
        uri = os.path.normpath(os.path.join(os.path.dirname(__file__), uri))
    path, fname = os.path.split(uri)
    mname, ext = os.path.splitext(fname)

    if os.path.exists(os.path.join(path,mname)+'.pyc'):
        try:
            return imp.load_compiled(mname, uri)
        except:
            pass
    if os.path.exists(os.path.join(path,mname)+'.py'):
        try:
            return imp.load_source(mname, uri)
        except:
            pass

    return mod
stamat
la source
0

La pièce ci-dessous a fonctionné pour moi:

>>>import imp; 
>>>fp, pathname, description = imp.find_module("/home/test_module"); 
>>>test_module = imp.load_module("test_module", fp, pathname, description);
>>>print test_module.print_hello();

si vous voulez importer en shell-script:

python -c '<above entire code in one line>'
PanwarS87
la source
-2

Par exemple, les noms de mes modules sont comme jan_module/ feb_module/ mar_module.

month = 'feb'
exec 'from %s_module import *'%(month)
chiru
la source
-7

Ce qui suit a fonctionné pour moi:

import sys, glob
sys.path.append('/home/marc/python/importtest/modus')
fl = glob.glob('modus/*.py')
modulist = []
adapters=[]
for i in range(len(fl)):
    fl[i] = fl[i].split('/')[1]
    fl[i] = fl[i][0:(len(fl[i])-3)]
    modulist.append(getattr(__import__(fl[i]),fl[i]))
    adapters.append(modulist[i]())

Il charge les modules du dossier «modus». Les modules ont une seule classe avec le même nom que le nom du module. Par exemple, le fichier modus / modu1.py contient:

class modu1():
    def __init__(self):
        self.x=1
        print self.x

Le résultat est une liste de classes "adaptateurs" chargés dynamiquement.

Marc de Lignie
la source
13
Veuillez ne pas enterrer les réponses sans fournir de raison dans les commentaires. Cela n'aide personne.
Rebs
Je voudrais savoir pourquoi c'est une mauvaise chose.
DevPlayer
Comme moi, puisque je suis nouveau sur Python et que je veux savoir pourquoi c'est mauvais.
Kosmos
5
C'est mauvais non seulement parce que c'est mauvais python, mais c'est aussi généralement mauvais code. Qu'est-ce que c'est fl? La définition de la boucle for est trop complexe. Le chemin est codé en dur (et par exemple, non pertinent). Il utilise python ( __import__) découragé , à quoi ça fl[i]sert? C'est fondamentalement illisible, et est inutilement complexe pour quelque chose qui n'est pas si difficile - voir la réponse la plus votée avec sa ligne unique.
Phil