2008-11-19 18 views
363

Piszę aplikację Pythona, który przyjmuje jako polecenie jako argument, na przykład:moduł dynamiczny import w Pythonie

$ python myapp.py command1 

chcę aplikacji są rozciągliwe, to znaczy, aby móc dodać 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:

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

Obecnie ten realizowany jest 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, jestem po prostu zastanawiasz się, czy jest możliwe bardziej idiomatyczne drogą do osiągnięcia tego, co robimy z tym kodem.

Należy zauważyć, że szczególnie nie chcę się przyzwyczaić do używania jaj lub punktów rozszerzenia. To nie jest projekt open-source i nie oczekuję, że będą to "wtyczki". Chodzi o to, aby uprościć główny kod aplikacji i usunąć potrzebę jej modyfikacji za każdym razem, gdy dodawany jest nowy moduł poleceń.

+0

Czego z_listy = [ "myapp.commands"] zrobić? –

+0

@ PieterMüller: w powłoce Pythona wpisz: '' dir (__ import __) ''. Lista powinna zawierać listę nazw do naśladowania "od nazwy import ...". – mawimawi

+2

[Importuj moduł ze zmiennej łańcuchowej] (http://stackoverflow.com/questions/8718885/import-module-z-strumienia-zmiennego) –

Odpowiedz

228

Pythona starsze niż 2.7/3.1, to dość dużo, jak to zrobić. W przypadku nowszych wersji, zobacz importlib.import_modulefor 2.7+ i for 3.1+.

Możesz użyć exec, jeśli chcesz również.

Uwaga można importować listę modułów w ten sposób:

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

Ripped prosto z Dive Into Python.

+5

jaka jest różnica w stosunku do exec? – user1767754

+0

Jak można użyć '__init__' tego modułu? –

+2

Jednym z problemów z tym rozwiązaniem dla OP jest to, że zalewkowanie wyjątków dla jednego lub dwóch złych modułów "poleceń" powoduje, że jego cała aplikacja-komenda zawodzi z jednym wyjątkiem. Osobiście szukałem pętli po każdym __import__ pojedynczo zapakowanym w try: mods = __ import __() \ nexcept ImportError jako błąd: raport (błąd), aby inne polecenia nadal działały, a złe zostały naprawione. – DevPlayer

9

Można użyć exec:

exec "import myapp.commands.%s" % command 
+0

Jak uzyskać uchwyt do modułu przez exec, dzięki czemu mogę zadzwonić (w tym przykładzie) metoda .run()? –

+5

Możesz wtedy wykonać getattr (myapp.commands, command), aby uzyskać dostęp do modułu. –

+1

... lub dodaj 'as command_module' na końcu instrukcji import, a następnie wykonaj polecenie' command_module.run() ' – oxfn

117

Jak wspomniano moduł imp zapewnia funkcje ładowania:

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

Użyłem tych przed wykonać coś podobnego.

W moim przypadku zdefiniowałem określoną klasę z określonymi metodami, które były wymagane. Raz załadowany moduł Chciałbym sprawdzić, czy klasa była w module, a następnie utworzyć instancję tej klasy, coś takiego:

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 
+1

Dobre i proste rozwiązanie. Napisałem podobną: http://stamat.wordpress.com/dynamic-module-import-in-python/ Ale twoje ma kilka wad: A co z wyjątkami? IOError i ImportError? Dlaczego nie sprawdzić najpierw skompilowanej wersji, a następnie wersji źródłowej. Oczekiwanie klasy zmniejsza możliwość ponownego użycia w twoim przypadku. – stamat

+2

W linii, w której tworzysz MyClass w module docelowym, dodajesz redundantne odniesienie do nazwy klasy. Jest już przechowywany w 'expected_class', więc możesz zamiast tego użyć' class_inst = getattr (py_mod, expected_name)() '. – Amoss

+1

* Pamiętaj, że jeśli istnieje poprawnie dopasowany plik skompilowany w bajtach (z sufiksem .pyc lub .pyo), zostanie on użyty zamiast analizowania podanego pliku źródłowego *. [https://docs.python.org/2/library/imp.html#imp.load_source](https://docs.python.org/2/library/imp.html#imp.load_source) – cdosborn

0

Brzmi to, co naprawdę chcesz to architektura wtyczek.

Powinieneś rzucić okiem na funkcjonalność entry points dostarczoną przez pakiet setuptools. Oferuje świetny sposób na odkrycie wtyczek załadowanych do twojej aplikacji.

+6

Wspomniałem, że nie mam chcą używać punktów wejścia i architektury typu wtyczki. Powodem tego projektu jest większa łatwość obsługi kodu i modułowość, niż możliwość dołączania dowolnych wtyczek. –

-6

Następujące pracował dla mnie:

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ą pojedynczą klasę o tej samej nazwie co nazwa modułu. Na przykład. plik modus/modu1.py zawiera:

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

Wynikiem jest lista dynamicznie ładowanych "adapterów" klas.

+12

Proszę nie zakopywać odpowiedzi bez podania przyczyny w komentarzach. To nikomu nie pomaga. – Rebs

+0

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

+0

Tak samo jak ja, odkąd jestem nowy w Pythonie i chcę wiedzieć, dlaczego to jest złe. – Kosmos

13

Jeśli chcesz to w twoich 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)]' 

samo będzie działać z globals()

206

zalecany sposób dla Pythona 2.7 i nowszym jest użycie importlib moduł:

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

Zalecane przez jakie źródło lub organ? – michuelnik

+42

Dokumentacja doradza [przeciw] (https://docs.python.org/2/library/functions.html#__import__) za pomocą funkcji "__import__" na rzecz wyżej wymienionego modułu. –

+5

Działa to dobrze dla importowania 'os.path'; co powiesz na 'from os.path import *? –

3

Podobne jako rozwiązanie @monkut, ale możliwe do ponownego wykorzystania i tolerancji błędów 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 
0

Poniższy kawałek pracował 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 importować w skrypt powłoki:

python -c '<above entire code in one line>' 
Powiązane problemy