Jak zaimportować moduł, który ma nazwę jako ciąg?


543

Piszę aplikację w języku Python, która przyjmuje jako polecenie argument, na przykład:

$ python myapp.py command1

Chcę, aby aplikacja była rozszerzalna, to znaczy mogła dodawać nowe moduły, które implementują nowe polecenia bez konieczności zmiany głównego źródła aplikacji. Drzewo wygląda mniej więcej tak:

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

Chcę więc, aby aplikacja znalazła dostępne moduły poleceń w czasie wykonywania i uruchomiła odpowiedni.

Python definiuje funkcję __import__ , która pobiera ciąg nazwy modułu:

__import __ (name, globals = None, locals = None, fromlist = (), level = 0)

Funkcja importuje nazwę modułu, potencjalnie używając danych globalnych i lokalnych do określenia, jak interpretować nazwę w kontekście pakietu. Fromlist podaje nazwy obiektów lub podmodułów, które powinny zostać zaimportowane z modułu podanego przez name.

Źródło: https://docs.python.org/3/library/functions.html# import

Więc obecnie mam coś takiego:

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

command_module.run()

Działa to dobrze, zastanawiam się tylko, czy jest jakiś bardziej idiomatyczny sposób na osiągnięcie tego, co robimy z tym kodem.

Zauważ, że szczególnie nie chcę wchodzić w użycie jaj lub punktów rozszerzenia. To nie jest projekt typu open source i nie oczekuję, że pojawią się „wtyczki”. Chodzi o to, aby uprościć główny kod aplikacji i wyeliminować potrzebę jego modyfikacji za każdym razem, gdy dodawany jest nowy moduł poleceń.


Co robi fromlist = ["myapp.commands"]?
Pieter Müller,

@ PieterMüller: w powłoce Pythona wpisz: dir(__import__). Lista from powinna być listą nazw do emulacji „z importu nazw ...”.
mawimawi


Od 2019 r. Powinieneś poszukać importlib: stackoverflow.com/a/54956419/687896
Brandt

Nie używaj __import__ patrz Python Doc użyj importlib.import_module
fcm

Odpowiedzi:


321

W przypadku języka Python starszego niż 2.7 / 3.1 tak to robisz.

Dla nowszych wersjach, patrz importlib.import_modulena Pythonie 2 i i Pythona 3 .

Możesz użyć, execjeśli chcesz.

Lub za pomocą __import__możesz zaimportować listę modułów, wykonując następujące czynności:

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

Zgrywanie prosto z Dive Into Python .


7
Jaka jest różnica do wykonania?
user1767754,

1
Jak mógłbyś skorzystać __init__z tego modułu?
Dorian Dore

7
Jednym z problemów związanych z tym rozwiązaniem dla OP jest to, że wychwytywanie wyjątków dla jednego lub dwóch złych modułów „komend” powoduje, że jego / jej cała aplikacja-komenda zawodzi z powodu jednego wyjątku. Osobiście chciałbym zapętlić każdy import osobno zapakowany w try: mods = __ import __ () \ nexcept ImportError jako błąd: zgłoś (błąd), aby umożliwić innym poleceniom kontynuowanie działania, podczas gdy złe zostaną naprawione.
DevPlayer

7
Innym problemem związanym z tym rozwiązaniem, jak zauważa poniżej Denis Malinowski, jest to, że sami doktorzy python nie zalecają używania __import__. Dokumentacja 2.7: „Ponieważ ta funkcja jest przeznaczona do użycia przez interpreter Pythona, a nie do ogólnego użytku, lepiej jest użyć importlib.import_module () ...” Podczas używania Python3 impmoduł rozwiązuje tę proble, jak wspomina poniżej monkut.
LiavK,

2
@JohnWu dlaczego potrzebujesz, foreachkiedy masz for element ini map()chociaż :)
cowbert

300

Zalecanym sposobem dla Python 2.7 i 3.1 i nowszych jest użycie importlibmodułu:

importlib.import_module (name, package = None)

Zaimportuj moduł. Argument name określa, który moduł ma zostać zaimportowany w kategoriach bezwzględnych lub względnych (np. Pkg.mod lub ..mod). Jeśli nazwa jest podana w kategoriach względnych, argument pakietu musi być ustawiony na nazwę pakietu, który ma pełnić rolę kotwicy przy rozwiązywaniu nazwy pakietu (np. Moduł_importu („.. mod”, „pkg.subpkg”) zaimportuje pkg.mod).

na przykład

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

2
Zalecane przez które źródło lub organ?
michuelnik

66
Dokumentacja radzi przed użyciem __import__funkcji na rzecz wyżej wymienionego modułu.
Denis Malinovsky

8
Działa to dobrze w przypadku importu os.path; jak o from os.path import *?
Nam G VU

5
Otrzymuję odpowiedź tutaj stackoverflow.com/a/44492879/248616 tj. dzwoniącyglobals().update(my_module.__dict)
Nam G VU

2
@NamGVU, jest to niebezpieczna metoda, ponieważ zanieczyszcza twoje globale i może zastąpić wszelkie identyfikatory o tych samych nazwach. Link, który opublikowałeś ma lepszą, ulepszoną wersję tego kodu.
Denis Malinovsky

132

Uwaga: imp jest przestarzałe od Python 3.4 na rzecz importlib

Jak wspomniano, moduł imp zapewnia funkcje ładowania:

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

Użyłem ich wcześniej, aby wykonać coś podobnego.

W moim przypadku zdefiniowałem konkretną klasę za pomocą zdefiniowanych metod, które były wymagane. Po załadowaniu modułu sprawdziłbym, czy klasa była w module, a następnie utworzyłem instancję tej klasy, mniej więcej tak:

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
Dobre i proste rozwiązanie. Napisałem podobny: stamat.wordpress.com/dynamic-module-import-in-python Ale twoje ma pewne wady: co z wyjątkami? IOError i ImportError? Sprawdź najpierw wersję skompilowaną, a następnie wersję źródłową. Oczekiwanie na klasę zmniejsza możliwość ponownego użycia w twoim przypadku.
stamat

3
W linii, w której konstruujesz MyClass w module docelowym, dodajesz nadmiarowe odwołanie do nazwy klasy. Jest już zapisany, expected_classwięc możesz to zrobić class_inst = getattr(py_mod,expected_name)()zamiast tego.
Andrew

1
Zauważ, że jeśli istnieje poprawnie dopasowany plik skompilowany bajtowo (z przyrostkiem .pyc lub .pyo), zostanie on użyty zamiast parsowania podanego pliku źródłowego . https://docs.python.org/2/library/imp.html#imp.load_source
cdosborn

1
Uwaga: to rozwiązanie działa; jednak moduł imp zostanie wycofany na korzyść importu lib, patrz strona imp: "" "Przestarzały od wersji 3.4: Pakiet imp oczekuje na wycofanie na korzyść importlib." ""
Ricardo


15

W dzisiejszych czasach powinieneś używać importlib .

Zaimportuj plik źródłowy

W docs faktycznie stanowić receptę na to, i to idzie tak:

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)
sys.modules[module_name] = module
spec.loader.exec_module(module)
from pluginX import your_stuff


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

Zaimportuj pakiet

Importowanie pakiet ( np , pluginX/__init__.py) w ramach bieżącego katalogu jest w rzeczywistości bardzo proste:

import importlib

pkg = importlib.import_module('pluginX')

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

3
dodaj sys.modules[module_name] = modulena końcu kodu, jeśli chcesz móc import module_namepóźniej
Daniel Braun

@DanielBraun robi to, więc dostaję błąd> ModuleNotFoundError
Nam G VU

11

Jeśli chcesz to w lokalnych mieszkańców:

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

to samo działałoby globals()


Dokumentacja dla wbudowanej locals()funkcji wyraźnie ostrzega, że „zawartość tego słownika nie powinna być modyfikowana”.
martineau

9

Możesz użyć exec:

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

Jak uzyskać uchwyt do modułu poprzez exec, aby móc wywołać (w tym przykładzie) metodę .run ()?
Kamil Kisiel,

5
Następnie możesz wykonać polecenie getattr (myapp.commands, command), aby uzyskać dostęp do modułu.
Greg Hewgill

1
... lub dodaj as command_modulena końcu instrukcji importu, a następnie zróbcommand_module.run()
oxfn

W niektórych wersjach języka Python 3+ exec został przekonwertowany na funkcję z dodatkową korzyścią, że wynikowy odnośnik utworzony w źródle jest przechowywany w argumencie locals () funkcji exec (). Aby dodatkowo odizolować plik exec, do którego odnosi się odwołanie, z miejscowych bloków kodu lokalnego, możesz podać swój własny dykt, na przykład pusty, i odwołać się do referencji za pomocą tego dykta lub przekazać go do innych funkcji exec (source, gobals (), command1_dict) .... print (command1_dict ['somevarible'])
DevPlayer

3

Podobne jak rozwiązanie @monkut, ale wielokrotnego użytku i odporne na błędy opisane tutaj 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

1

Poniższy kawałek działał dla mnie:

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

jeśli chcesz zaimportować w skrypcie powłoki:

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

-2

Na przykład moje nazwy modułów są takie jak jan_module/ feb_module/ mar_module.

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

-7

Dla mnie działało:

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

Ładuje moduły z folderu „modus”. Moduły mają jedną klasę o tej samej nazwie co nazwa modułu. Np. Plik modus / moduł1.py zawiera:

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

Wynikiem jest lista dynamicznie ładowanych klas „adapterów”.


13
Nie chowaj odpowiedzi bez podania przyczyny w komentarzach. To nikomu nie pomaga.
Rebs

Chciałbym wiedzieć, dlaczego to złe rzeczy.
DevPlayer

Tak samo jak ja, ponieważ jestem nowy w Pythonie i chcę wiedzieć, dlaczego to jest złe.
Kosmos,

5
Jest to złe nie tylko dlatego, że jest to zły python, ale także ogólnie zły kod. Co to jest fl? Definicja pętli for jest zbyt skomplikowana. Ścieżka jest zakodowana na stałe (i na przykład nieistotna). Używa zniechęconego Pythona ( __import__), po co to wszystko fl[i]? Jest to w zasadzie nieczytelne i jest niepotrzebnie skomplikowane w przypadku czegoś, co nie jest wcale takie trudne - zobacz najlepiej głosowaną odpowiedź za pomocą jednej linijki.
Phil
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.