Come importare un modulo dato il suo nome come stringa?


543

Sto scrivendo un'applicazione Python che accetta come comando come argomento, ad esempio:

$ python myapp.py command1

Voglio che l'applicazione sia estensibile, cioè per poter aggiungere nuovi moduli che implementano nuovi comandi senza dover cambiare l'origine dell'applicazione principale. L'albero è simile a:

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

Quindi voglio che l'applicazione trovi i moduli di comando disponibili in fase di esecuzione ed esegua quello appropriato.

Python definisce una funzione __import__ , che accetta una stringa per il nome di un modulo:

__import __ (nome, globali = Nessuno, locali = Nessuno, dalla lista = (), livello = 0)

La funzione importa il nome del modulo, utilizzando potenzialmente i globali e i locali indicati per determinare come interpretare il nome in un contesto di pacchetto. La fromlist fornisce i nomi di oggetti o sottomoduli che dovrebbero essere importati dal modulo dato dal nome.

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

Quindi attualmente ho qualcosa del tipo:

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

command_module.run()

Funziona bene, mi chiedo solo se esiste forse un modo più idiomatico per realizzare ciò che stiamo facendo con questo codice.

Nota che non voglio specificamente usare uova o punti di estensione. Questo non è un progetto open source e non mi aspetto che ci siano "plugin". Il punto è semplificare il codice principale dell'applicazione e rimuovere la necessità di modificarlo ogni volta che viene aggiunto un nuovo modulo di comando.


Che cosa fa fromlist = ["myapp.commands"]?
Pieter Müller,

@ PieterMüller: in un guscio pitone digitate: dir(__import__). La fromlist dovrebbe essere una lista di nomi da emulare "dall'importazione dei nomi ...".
mawimawi,


A partire dal 2019, dovresti cercare importlib: stackoverflow.com/a/54956419/687896
Brandt

Non usare __import__ vedi Python Doc usa un importlib.import_module
fcm

Risposte:


321

Con Python più vecchio di 2.7 / 3.1, è praticamente come lo fai.

Per le versioni più recenti, vedere importlib.import_moduleper Python 2 e Python 3 .

Puoi usarlo anche execse lo desideri.

O usando __import__puoi importare un elenco di moduli in questo modo:

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

Strappato direttamente da Dive Into Python .


7
qual è il differece da exec?
user1767754,

1
Come hai potuto usare __init__quel modulo?
Dorian Dore,

7
Un problema con questa soluzione per l'OP è che intercettare le eccezioni per uno o due moduli "di comando" errati fa fallire la sua intera app di comando su una sola eccezione. Personalmente farei il ciclo su ogni importazione singolarmente racchiusa in un tentativo: mods = __ import __ () \ nexcept ImportError come errore: report (errore) per consentire ad altri comandi di continuare a funzionare mentre quelli cattivi vengono riparati.
DevPlayer

7
Un altro problema con questa soluzione, come sottolinea Denis Malinovsky di seguito, è che gli stessi documenti di Python consigliano di non utilizzare __import__. Documenti 2.7: "Poiché questa funzione è pensata per l'uso dall'interprete Python e non per un uso generale, è meglio usare importlib.import_module () ..." Quando si usa python3, il impmodulo risolve questo problema, come menzionato di seguito da monkut.
LiavK,

2
@JohnWu perché hai bisogno foreachquando hai for element ine map()comunque :)
cowbert,

300

Il modo consigliato per Python 2.7 e 3.1 e versioni successive è utilizzare il importlibmodulo:

importlib.import_module (nome, pacchetto = Nessuno)

Importa un modulo. L'argomento name specifica quale modulo importare in termini assoluti o relativi (es. Pkg.mod o ..mod). Se il nome è specificato in termini relativi, l'argomento del pacchetto deve essere impostato sul nome del pacchetto che deve fungere da ancoraggio per la risoluzione del nome del pacchetto (ad esempio import_module ('.. mod', 'pkg.subpkg') importerà pkg.mod).

per esempio

my_module = importlib.import_module('os.path')

2
Consigliato da quale fonte o autorità?
michuelnik,

66
Documentazione consiglia contro utilizzando __import__funzione a favore del suddetto modulo.
Denis Malinovsky,

8
Questo funziona bene per l'importazione os.path; che ne dici from os.path import *?
Nam G VU,

5
Ottengo la risposta qui stackoverflow.com/a/44492879/248616 ie. chiamandoglobals().update(my_module.__dict)
Nam G VU il

2
@NamGVU, questo è un metodo pericoloso poiché inquina i tuoi globuli e può sovrascrivere qualsiasi identificatore con lo stesso nome. Il link che hai pubblicato ha una versione migliore e migliorata di questo codice.
Denis Malinovsky,

132

Nota: imp è deprecato da Python 3.4 a favore di importlib

Come accennato, il modulo imp offre funzioni di caricamento:

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

Ho usato questi prima per eseguire qualcosa di simile.

Nel mio caso ho definito una classe specifica con metodi definiti che erano richiesti. Una volta caricato il modulo, verificherei se la classe era nel modulo e quindi creavo un'istanza di quella classe, qualcosa del genere:

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

2
Soluzione buona e semplice. Ne abbiamo scritto uno simile: stamat.wordpress.com/dynamic-module-import-in-python Ma il tuo ha alcuni difetti: che dire delle eccezioni? IOError e ImportError? Perché non verificare prima la versione compilata e poi la versione di origine. Aspettarsi una lezione riduce la riusabilità nel tuo caso.
stamat,

3
Nella riga in cui costruisci MyClass nel modulo di destinazione stai aggiungendo un riferimento ridondante al nome della classe. È già memorizzato in expected_classmodo da poter class_inst = getattr(py_mod,expected_name)()invece fare .
Andrew,

1
Si noti che se esiste un file compilato in byte correttamente corrispondente (con suffisso .pyc o .pyo), verrà utilizzato invece di analizzare il file sorgente specificato . https://docs.python.org/2/library/imp.html#imp.load_source
cdosborn

1
Heads up: questa soluzione funziona; tuttavia, il modulo imp sarà deprecato a favore di import lib, consultare la pagina imp: "" "Obsoleto dalla versione 3.4: il pacchetto imp è in attesa di deprecazioni a favore di importlib." ""
Ricardo


15

Al giorno d'oggi dovresti usare importlib .

Importa un file di origine

I documenti in realtà forniscono una ricetta per questo, e va come:

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)

In questo modo puoi accedere ai membri (es. Una funzione " hello") dal tuo modulo pluginX.py- in questo snippet chiamato module- sotto il suo spazio dei nomi; Ad es module.hello().

Se si desidera importare i membri (ad es. " hello") È possibile includere module/ pluginXnell'elenco dei moduli in memoria:

sys.modules[module_name] = module

from pluginX import hello
hello()

Importa un pacchetto

L'importazione di un pacchetto ( ad es. , pluginX/__init__.py) Nella directory corrente è in realtà semplice:

import importlib

pkg = importlib.import_module('pluginX')

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

3
aggiungere sys.modules[module_name] = modulealla fine del codice se si desidera poterlo fare import module_namesuccessivamente
Daniel Braun,

@DanielBraun in questo modo fammi errore> ModuleNotFoundError
Nam G VU

10

Se lo vuoi nella tua gente del posto:

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

lo stesso funzionerebbe con globals()


2
La documentazione per la locals()funzione integrata avverte esplicitamente che il "contenuto di questo dizionario non deve essere modificato".
martineau,

9

Puoi usare exec:

exec("import myapp.commands.%s" % command)

Come ottengo un handle per il modulo tramite exec, in modo da poter chiamare (in questo esempio) il metodo .run ()?
Kamil Kisiel,

5
È quindi possibile eseguire getattr (myapp.commands, comando) per accedere al modulo.
Greg Hewgill,

1
... o aggiungi as command_modulealla fine della dichiarazione di importazione e poi faicommand_module.run()
oxfn

In alcune versioni di Python 3+ exec è stato convertito in una funzione con l'ulteriore vantaggio che il risultante riferimento creato nella sorgente viene archiviato nell'argomento locals () di exec (). Per isolare ulteriormente il exec fatto riferimento dai locali del blocco di codice locale è possibile fornire il proprio dict, come uno vuoto e fare riferimento ai riferimenti usando quel dict, oppure passare quel dict ad altre funzioni exec (source, gobals (), command1_dict) .... print (command1_dict ['somevarible'])
DevPlayer

2

Simile alla soluzione di @monkut ma riutilizzabile e tollerante agli errori qui descritto 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

0

Il pezzo sotto ha funzionato per me:

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

se vuoi importare in shell-script:

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

-2

Ad esempio, i nomi dei miei moduli sono come jan_module/ feb_module/ mar_module.

month = 'feb'
exec 'from %s_module import *'%(month)

-7

Per me ha funzionato:

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

Carica i moduli dalla cartella 'modus'. I moduli hanno una singola classe con lo stesso nome del nome del modulo. Ad esempio il file modus / modu1.py contiene:

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

Il risultato è un elenco di "adattatori" di classi caricate dinamicamente.


13
Si prega di non seppellire le risposte senza fornire un motivo nei commenti. Non aiuta nessuno.
Rebs,

Vorrei sapere perché questa è roba cattiva.
DevPlayer

Come me, dato che sono nuovo su Python e voglio sapere perché questo è negativo.
Kosmos,

5
Questo non è male solo perché è un cattivo pitone, ma è anche un codice generalmente cattivo. Che cosa è fl? La definizione del ciclo for è eccessivamente complessa. Il percorso è hard coded (e, per esempio, irrilevante). Usa scoraggiato python ( __import__), a cosa servono tutte queste fl[i]cose? Questo è fondamentalmente illeggibile, ed è inutilmente complesso per qualcosa che non è poi così difficile - vedi la risposta più votata con il suo one-liner.
Phil
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.