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ń.

Kamil Kisiel
źródło
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 .

Harley Holcombe
źródło
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')
Denis Malinowski
źródło
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
monkut
źródło
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)
Brandt
źródło
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()

Jonathan
źródło
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)
Greg Hewgill
źródło
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
stamat
źródło
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>'
PanwarS87
źródło
-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)
Chiru
źródło
-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”.

Marc de Lignie
źródło
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