Как импортировать модуль по названию?
Я пишу приложение на Python, которое принимает в качестве аргумента команду, например:
$ python myapp.py command1
Я хочу, чтобы приложение было расширяемым, то есть чтобы можно было добавлять новые модули, реализующие новые команды, без необходимости изменения основного источника приложения. Дерево выглядит примерно так:
myapp/
__init__.py
commands/
__init__.py
command1.py
command2.py
foo.py
bar.py
Поэтому я хочу, чтобы приложение находило доступные командные модули во время выполнения и выполняло соответствующий.
Python определяет функцию __import__, которая принимает строку для имени модуля:
__import __ (name, globals = None, localals=None, fromlist=(), level=0)
Функция импортирует имя модуля, потенциально используя данные глобальные и локальные значения, чтобы определить, как интерпретировать имя в контексте пакета. Список from дает имена объектов или подмодулей, которые должны быть импортированы из модуля, заданного именем.
В настоящее время у меня есть что-то вроде:
command = sys.argv[1]
try:
command_module = __import__("myapp.commands.%s" % command, fromlist=["myapp.commands"])
except ImportError:
# Display error message
command_module.run()
Это прекрасно работает, мне просто интересно, есть ли, возможно, более идиоматический способ выполнить то, что мы делаем с этим кодом.
Обратите внимание, что я специально не хочу использовать яйца или точки расширения. Это не проект с открытым исходным кодом, и я не ожидаю, что будут "плагины". Смысл в том, чтобы упростить основной код приложения и избавить от необходимости изменять его каждый раз, когда добавляется новый командный модуль.
12 ответов
С Python старше 2.7/3.1 это в значительной степени то, как вы это делаете.
Для более новых версий, см. importlib.import_module
для Python 2 и и Python 3.
Ты можешь использовать exec
если ты тоже хочешь.
Или используя __import__
Вы можете импортировать список модулей, выполнив это:
>>> moduleNames = ['sys', 'os', 're', 'unittest']
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)
Разорвал прямо из Dive Into Python.
Рекомендуемый способ для Python 2.7 и 3.1 и выше - использовать importlib
модуль:
importlib.import_module (имя, пакет = нет)
Импортировать модуль. Аргумент name указывает, какой модуль импортировать в абсолютном или относительном выражении (например, pkg.mod или..mod). Если имя указано в относительных терминах, то аргумент пакета должен быть установлен на имя пакета, которое должно выступать в качестве якоря для разрешения имени пакета (например, import_module('..mod', 'pkg.subpkg') будет импортировать pkg.mod).
например
my_module = importlib.import_module('os.path')
Примечание: начиная с Python 3.4 imp устарела в пользу importlib
Как уже упоминалось, модуль 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
В настоящее время вы должны использовать importlib.
Импортировать исходный файл
Документы фактически обеспечивают рецепт для этого, и это выглядит так:
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)
Импортировать пакет
Импорт пакета (например, pluginX/__init__.py
) под вашим текущим каталогом это на самом деле просто:
import importlib
pluginX = importlib.import_module('pluginX')
# check if it's all there..
def bla(mod):
print(dir(mod))
bla(module)
Используйте модуль Imp, или более прямой __import__()
функция.
Если вы хотите это в ваших местных жителей:
>>> 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()
Ты можешь использовать exec
:
exec "import myapp.commands.%s" % command
Аналогично решению @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
Следующая часть работала для меня:
>>>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();
если вы хотите импортировать в shell-скрипт:
python -c '<above entire code in one line>'
It sounds like what you really want is a plugin architecture.
Вы должны взглянуть на функциональность точек входа, предоставляемых пакетом setuptools. Он предлагает отличный способ обнаружить плагины, которые загружаются для вашего приложения.
Например: мои имена модулей похожи на jan_module/ feb_module/ mar_module
месяц = "февраля"
exec 'из%s_module import *'%(месяц)
Следующее работало для меня:
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]())
Загружает модули из папки 'modus'. Модули имеют один класс с тем же именем, что и имя модуля. Например, файл modus/modu1.py содержит:
class modu1():
def __init__(self):
self.x=1
print self.x
В результате получается список динамически загружаемых классов "адаптеров".