2008-11-19 4 views
363

Я пишу приложение на Python, который принимает в качестве команды в качестве аргумента, например:Динамический модуль в Python

$ python myapp.py command1 

Я хочу, чтобы приложение быть расширяемой, то есть, чтобы иметь возможность добавлять новые модули, которые реализуют новые команды без изменения основного источника приложения. Дерево выглядит примерно так:

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

Так что я хочу приложение, чтобы найти доступные командные модули во время выполнения и выполнить соответствующую одну.

В настоящее время это реализовано что-то вроде:

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

command_module.run() 

Это прекрасно работает, я просто интересно, если есть, возможно, более идиоматический способ сделать то, что мы делаем с этим кодом.

Обратите внимание, что я специально не хочу использовать яйца или дополнительные точки. Это не проект с открытым исходным кодом, и я не ожидаю, что там будут «плагины». Дело в том, чтобы упростить основной код приложения и удалить необходимость его изменения каждый раз при добавлении нового командного модуля.

+0

Что означает fromlist = [ "myapp.commands"] делать? – 2012-06-06 10:29:57

+0

@ PieterMüller: в оболочке python введите это: `` dir (__ import __) ``. Список из списка должен быть списком имен для эмуляции «from name import ...». – mawimawi 2012-08-09 07:13:14

+2

[Модуль импорта из строковой переменной] (http://stackoverflow.com/questions/8718885/import-module-from-string-variable) – 2015-08-12 18:28:00

ответ

228

С Python старше 2,7/3,1, что довольно много, как вы это делаете. Для более новых версий см. importlib.import_modulefor 2.7+ и for 3.1+.

Вы можете использовать exec, если хотите.

Примечание Вы можете импортировать список модулей, делая это:

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

Ripped прямо из Dive Into Python.

9

Вы можете использовать exec:

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

Как получить дескриптор модуля через exec, чтобы я мог позвонить (в этом примере) метод .run()? – 2008-11-19 06:19:10

+5

Затем вы можете получить getattr (myapp.commands, command) для доступа к модулю. – 2008-11-19 06:20:07

+1

... или добавить `как command_module` в конец оператора импорта, а затем выполнить команду` command_module.run() ` – oxfn 2013-10-16 10:43:34

117

Как упомянуто imp модуль предоставляет вам погрузочные функции:

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

Я использовал их прежде, чем выполнить что-то подобное.

В моем случае я определил определенный класс с определенными методами, которые были необходимы. После того, как я загрузил модуль я хотел бы проверить, если класс был в модуле, а затем создать экземпляр этого класса, что-то вроде этого:

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 
0

Похоже, что вы действительно хотите это плагин архитектуры.

Вы должны взглянуть на функциональность entry points, предоставляемую пакетом setuptools. Он предлагает отличный способ открыть плагины, загруженные для вашего приложения.

-6

Следующая работал для меня:

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

Он загружает модули из папки 'модус'. Модули имеют один класс с тем же именем, что и имя модуля. Например. файл модус/modu1.py содержит:

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

В результате список динамически загружаемых классов «адаптеров».

13

Если вы хотите в ваших местных жителей:

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

же будет работать с globals()

206

Рекомендуемый способ для Python 2.7 и более поздней версии, чтобы использовать importlib модуль:

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

Похожие как решение @monkut, но многоразовое и толерантное к ошибкам, описанное здесь 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

Ниже кусок работал для меня:

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

если вы хотите импортировать в раковине-скрипт:

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