Wie importiere ich ein Modul mit seinem Namen als Zeichenfolge?

543

Ich schreibe eine Python-Anwendung, die als Befehl ein Argument verwendet, zum Beispiel:

$ python myapp.py command1

Ich möchte, dass die Anwendung erweiterbar ist, dh neue Module hinzufügen kann, die neue Befehle implementieren, ohne die Hauptanwendungsquelle ändern zu müssen. Der Baum sieht ungefähr so ​​aus:

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

Daher möchte ich, dass die Anwendung zur Laufzeit die verfügbaren Befehlsmodule findet und das entsprechende ausführt.

Python definiert eine __import__ -Funktion, die eine Zeichenfolge für einen Modulnamen verwendet :

__import __ (Name, global = Keine, Einheimische = Keine, fromlist = (), Ebene = 0)

Die Funktion importiert den Modulnamen und verwendet möglicherweise die angegebenen globalen und lokalen Elemente, um zu bestimmen, wie der Name in einem Paketkontext interpretiert werden soll. Die fromlist gibt die Namen von Objekten oder Submodulen an, die aus dem Modul mit Namen importiert werden sollen.

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

Also habe ich momentan so etwas wie:

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

command_module.run()

Dies funktioniert einwandfrei. Ich frage mich nur, ob es möglicherweise einen idiomatischeren Weg gibt, um das zu erreichen, was wir mit diesem Code tun.

Beachten Sie, dass ich speziell nicht darauf eingehen möchte, Eier oder Verlängerungspunkte zu verwenden. Dies ist kein Open-Source-Projekt und ich erwarte keine "Plugins". Der Punkt besteht darin, den Hauptanwendungscode zu vereinfachen und die Notwendigkeit zu beseitigen, ihn jedes Mal zu ändern, wenn ein neues Befehlsmodul hinzugefügt wird.

Kamil Kisiel
quelle
Was macht die fromlist = ["myapp.commands"]?
Pieter Müller
@ PieterMüller: Geben Sie in einer Python-Shell Folgendes ein : dir(__import__). Die fromlist sollte eine Liste von Namen sein, die "from name import ..." emuliert werden sollen.
Mawimawi
Ab 2019 sollten Sie suchen nach importlib: stackoverflow.com/a/54956419/687896
Brandt
Verwenden Sie nicht __import__, siehe Python Doc. Verwenden Sie ein importlib.import_module
fcm

Antworten:

321

Mit Python älter als 2.7 / 3.1 machen Sie das so.

Bei neueren Versionen finden Sie importlib.import_modulefür Python 2 und und Python 3 .

Sie können verwenden, execwenn Sie möchten.

Oder __import__Sie können eine Liste von Modulen importieren, indem Sie folgende Schritte ausführen:

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

Direkt von Dive Into Python gerissen .

Harley Holcombe
quelle
7
Was ist der Unterschied zu Exec?
user1767754
1
Wie können Sie __init__dieses Modul verwenden?
Dorian Dore
7
Ein Problem bei dieser Lösung für das OP besteht darin, dass durch das Abfangen von Ausnahmen für ein oder zwei fehlerhafte "Befehls" -Module die gesamte Befehls-App bei einer Ausnahme fehlschlägt. Persönlich würde ich jeden Import einzeln durchlaufen, der in einem Versuch verpackt ist: mods = __ import __ () \ nexcept ImportError als Fehler: report (error), damit andere Befehle weiterarbeiten können, während die fehlerhaften behoben werden.
DevPlayer
7
Ein weiteres Problem bei dieser Lösung ist, wie Denis Malinovsky weiter unten ausführt, dass die Python-Dokumente selbst empfehlen, sie nicht zu verwenden __import__. Die 2.7-Dokumente: "Da diese Funktion für den Python-Interpreter und nicht für den allgemeinen Gebrauch bestimmt ist, ist es besser, importlib.import_module () zu verwenden ..." Bei Verwendung von python3 implöst das Modul dieses Problem, wie monkut unten erwähnt.
LiavK
2
@ JohnWu warum brauchst du, foreachwenn du hast for element inund map()obwohl :)
Cowbert
300

Die empfohlene Methode für Python 2.7 und 3.1 und höher ist die Verwendung des importlibModuls:

importlib.import_module (Name, Paket = Keine)

Importieren Sie ein Modul. Das Argument name gibt an, welches Modul absolut oder relativ importiert werden soll (z. B. pkg.mod oder ..mod). Wenn der Name relativ angegeben wird, muss das Paketargument auf den Namen des Pakets gesetzt werden, das als Anker für die Auflösung des Paketnamens dienen soll (z. B. import_module ('.. mod', 'pkg.subpkg'). wird pkg.mod importieren).

z.B

my_module = importlib.import_module('os.path')
Denis Malinovsky
quelle
2
Empfohlen von welcher Quelle oder Behörde?
michuelnik
66
Dokumentation berät gegen Verwendung __import__Funktion zugunsten der oben genannten Moduls.
Denis Malinovsky
8
Dies funktioniert gut für den Import os.path; wie wäre es from os.path import *?
Nam G VU
5
Ich bekomme die Antwort hier stackoverflow.com/a/44492879/248616 dh. Anrufglobals().update(my_module.__dict)
Nam G VU
2
@NamGVU, dies ist eine gefährliche Methode, da sie Ihre globalen Daten verschmutzt und alle gleichnamigen Bezeichner überschreiben kann. Der Link, den Sie gepostet haben, enthält eine bessere, verbesserte Version dieses Codes.
Denis Malinovsky
132

Hinweis: imp ist seit Python 3.4 zugunsten von importlib veraltet

Wie bereits erwähnt, bietet Ihnen das imp- Modul Ladefunktionen :

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

Ich habe diese schon einmal benutzt, um etwas Ähnliches zu machen.

In meinem Fall habe ich eine bestimmte Klasse mit definierten Methoden definiert, die erforderlich waren. Sobald ich das Modul geladen habe, überprüfe ich, ob sich die Klasse im Modul befindet, und erstelle dann eine Instanz dieser Klasse, etwa so:

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
Mönch
quelle
2
Gute und einfache Lösung. Ich habe ein ähnliches geschrieben: stamat.wordpress.com/dynamic-module-import-in-python Aber Ihr hat einige Mängel: Was ist mit Ausnahmen? IOError und ImportError? Warum nicht zuerst nach der kompilierten Version und dann nach der Quellversion suchen? Das Erwarten einer Klasse verringert in Ihrem Fall die Wiederverwendbarkeit.
Stamat
3
In der Zeile, in der Sie MyClass im Zielmodul erstellen, fügen Sie dem Klassennamen einen redundanten Verweis hinzu. Es ist bereits in gespeichert, expected_classsodass Sie dies class_inst = getattr(py_mod,expected_name)()stattdessen tun können .
Andrew
1
Beachten Sie, dass eine ordnungsgemäß übereinstimmende bytekompilierte Datei (mit dem Suffix .pyc oder .pyo) verwendet wird, anstatt die angegebene Quelldatei zu analysieren . https://docs.python.org/2/library/imp.html#imp.load_source
cdosborn
1
Heads up: Diese Lösung funktioniert; Das imp-Modul wird jedoch zugunsten von import lib veraltet sein, siehe imp-Seite: "" Veraltet seit Version 3.4: Das imp-Paket muss noch zugunsten von importlib abgelehnt werden. ""
Ricardo
15

Heutzutage sollten Sie importlib verwenden .

Importieren Sie eine Quelldatei

Die Dokumente bieten tatsächlich ein Rezept dafür, und es geht so:

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)

Auf diese Weise können Sie hellovon Ihrem Modul aus pluginX.py- in diesem aufgerufenen Snippet module- unter seinem Namespace auf die Mitglieder (z. B. eine Funktion " ") zugreifen . ZB , module.hello().

Wenn Sie die Mitglieder importieren möchten (z. B. " hello"), können Sie module/ pluginXin die speicherinterne Liste der Module aufnehmen:

sys.modules[module_name] = module

from pluginX import hello
hello()

Importieren Sie ein Paket

Importieren eines Pakets ( zB , pluginX/__init__.py) unter dem aktuellen Verzeichnis ist eigentlich ganz einfach:

import importlib

pkg = importlib.import_module('pluginX')

# check if it's all there..
def bla(mod):
    print(dir(mod))
bla(pkg)
Brandt
quelle
3
fügen Sie sys.modules[module_name] = moduleam Ende des Codes , wenn Sie in der Lage sein wollen , import module_namedanach
Daniel Braun
@DanielBraun dabei bekomme ich Fehler> ModuleNotFoundError
Nam G VU vor
10

Wenn Sie es bei Ihren Einheimischen wollen:

>>> 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)]'

das gleiche würde funktionieren mit globals()

Jonathan
quelle
2
Die Dokumentation für die eingebaute locals()Funktion warnt ausdrücklich davor, dass "der Inhalt dieses Wörterbuchs nicht geändert werden sollte".
Martineau
9

Sie können verwenden exec:

exec("import myapp.commands.%s" % command)
Greg Hewgill
quelle
Wie erhalte ich über die exec ein Handle für das Modul, damit ich (in diesem Beispiel) die .run () -Methode aufrufen kann?
Kamil Kisiel
5
Sie können dann getattr (Befehl myapp.commands, Befehl) ausführen, um auf das Modul zuzugreifen.
Greg Hewgill
1
... oder as command_moduleam Ende der Importanweisung hinzufügen und dann tuncommand_module.run()
oxfn
In einigen Versionen von Python 3+ wurde exec in eine Funktion konvertiert, mit dem zusätzlichen Vorteil, dass die in der Quelle erstellte resultierende Referenz im Argument local () von exec () gespeichert wird. Um die Exec'd, auf die verwiesen wird, weiter von den lokalen Codeblock-Lokalen zu isolieren, können Sie Ihr eigenes Diktat wie ein leeres bereitstellen und die Referenzen mit diesem Diktat referenzieren oder dieses Diktat an andere Funktionen exec (source, gobals (), command1_dict) übergeben. .... print (command1_dict ['somevarible'])
DevPlayer
3

Ähnlich wie die Lösung von @monkut, jedoch wiederverwendbar und fehlertolerant, wie hier beschrieben. 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
quelle
1

Das folgende Stück hat für mich funktioniert:

>>>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();

Wenn Sie in Shell-Skript importieren möchten:

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

Zum Beispiel sind meine Modulnamen wie jan_module/ feb_module/ mar_module.

month = 'feb'
exec 'from %s_module import *'%(month)
Chiru
quelle
-7

Folgendes hat bei mir funktioniert:

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]())

Es lädt Module aus dem Ordner 'modus'. Die Module haben eine einzelne Klasse mit demselben Namen wie der Modulname. ZB enthält die Datei modus / modu1.py:

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

Das Ergebnis ist eine Liste der dynamisch geladenen Klassen "Adapter".

Marc de Lignie
quelle
13
Bitte begraben Sie Antworten nicht ohne Angabe von Gründen in den Kommentaren. Es hilft niemandem.
Rebs
Ich würde gerne wissen, warum das schlechte Sachen sind.
DevPlayer
Genauso wie ich, da ich neu in Python bin und wissen möchte, warum das schlecht ist.
Kosmos
5
Das ist nicht nur schlecht, weil es schlechtes Python ist, sondern auch allgemein schlechter Code. Was ist fl? Die for-Schleifendefinition ist zu komplex. Der Pfad ist fest codiert (und zum Beispiel irrelevant). Es verwendet entmutigtes Python ( __import__), wofür ist das alles fl[i]? Dies ist im Grunde genommen nicht lesbar und für etwas, das nicht allzu schwer ist, unnötig komplex - siehe die Antwort mit der höchsten Abstimmung mit ihrem Einzeiler.
Phil