Я пишу приложение Python, которое принимает команду в качестве аргумента, например:
$ python myapp.py command1
Я хочу, чтобы приложение было расширяемым, то есть чтобы иметь возможность добавлять новые модули, реализующие новые команды, без необходимости изменять основной источник приложения. Дерево выглядит примерно так:
myapp/
__init__.py
commands/
__init__.py
command1.py
command2.py
foo.py
bar.py
Поэтому я хочу, чтобы приложение находило доступные командные модули во время выполнения и выполняло соответствующий.
Python определяет функцию __импорт__, которая принимает строку в качестве имени модуля:
__import__(name, globals=None, locals=None, fromlist=(), level=0)
The function imports the module name, potentially using the given globals and locals to determine how to interpret the name in a package context. The fromlist gives the names of objects or submodules that should be imported from the module given by name.
Source: https://docs.python.org/3/library/functions.html#import
Итак, в настоящее время у меня есть что-то вроде:
command = sys.argv[1]
try:
command_module = __import__("myapp.commands.%s" % command, fromlist=["myapp.commands"])
except ImportError:
# Display error message
command_module.run()
Это прекрасно работает, мне просто интересно, есть ли более идиоматический способ выполнить то, что мы делаем с этим кодом.
Обратите внимание, что я специально не хочу использовать яйца или точки расширения. Это не проект с открытым исходным кодом, и я не ожидаю, что там будут «плагины». Дело в том, чтобы упростить основной код приложения и избавиться от необходимости изменять его каждый раз, когда добавляется новый командный модуль.
@ PieterMüller: в оболочке Python введите следующее: dir(__import__). Fromlist должен быть списком имён для имитации «from name import ...».
По состоянию на 2019 год вы должны искать importlib: stackoverflow.com/a/54956419/687896
Не используйте __import__, см. Документ Python, используйте importlib.import_module






exec("import myapp.commands.%s" % command)
Как мне получить дескриптор модуля через exec, чтобы я мог вызвать (в этом примере) метод .run ()?
Затем вы можете выполнить getattr (myapp.commands, command) для доступа к модулю.
... или добавьте as command_module в конец оператора импорта, а затем выполните command_module.run()
В некоторых версиях Python 3+ exec был преобразован в функцию с дополнительным преимуществом, заключающимся в том, что результирующая ссылка, созданная в источнике, сохраняется в аргументе locals () функции exec (). Чтобы еще больше изолировать exec'd, на который ссылается локальный блок кода, вы можете предоставить свой собственный dict, например пустой, и ссылаться на ссылки, используя этот dict, или передать этот dict другим функциям exec (source, gobals (), command1_dict) .... print (command1_dict ['somevarible'])
С 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)
Разорвал прямиком из Погрузитесь в Python.
в чем разница с exec?
Как вы могли использовать __init__ этого модуля?
Одна проблема с этим решением для OP заключается в том, что перехват исключений для одного или двух плохих «командных» модулей приводит к отказу всего его / ее командного приложения при одном исключении. Лично я бы перебирал каждый импорт, индивидуально завернутый в try: mods = __ import __ () \ nexcept ImportError as error: report (error), чтобы позволить другим командам продолжать работать, пока плохие исправляются.
Другая проблема с этим решением, как указывает Денис Малиновский ниже, заключается в том, что сами документы python рекомендуют не использовать __import__. Документы 2.7: «Поскольку эта функция предназначена для использования интерпретатором Python, а не для общего использования, лучше использовать importlib.import_module () ...» При использовании python3 модуль imp решает эту проблему, как упоминает ниже монкут .
fyi, это забавное "злоупотребление" картой :) Интересно, почему в python нет такой вещи, как foreach, хотя
@JohnWu зачем вам foreach, когда у вас есть for element in и map() :)
Я решил сэкономить время всем, кто пытается использовать автозаполнение с динамическими модулями с PyCharm. Не работает.
Обратите внимание, что карта НЕ будет работать в python 3, поскольку карта теперь использует ленивую оценку.
Используйте модуль imp или более прямую функцию __import__().
Не используйте __import__, см. Документ Python, используйте importlib.import_module
Note: imp is deprecated since Python 3.4 in favor of importlib
Как уже упоминалось, модуль чертенок предоставляет вам функции загрузки:
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
Хорошее и простое решение. Я написал похожий: stamat.wordpress.com/dynamic-module-import-in-python Но у вас есть некоторые недостатки: А как насчет исключений? IOError и ImportError? Почему бы не проверить сначала скомпилированную версию, а затем исходную. Ожидание класса снижает возможность повторного использования в вашем случае.
В строке, где вы создаете MyClass в целевом модуле, вы добавляете избыточную ссылку на имя класса. Он уже хранится в expected_class, поэтому вы можете использовать class_inst = getattr(py_mod,expected_name)().
Внимание: это решение работает; однако модуль imp будет устаревшим в пользу import lib, см. страницу imp: "" "Устарело с версии 3.4: пакет imp ожидает прекращения поддержки в пользу importlib." ""
Для меня сработало следующее:
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
Результатом является список динамически загружаемых классов «адаптеров».
Пожалуйста, не прячьте ответы без объяснения причин в комментариях. Это никому не помогает.
Я хотел бы знать, почему это плохо.
То же, что и я, поскольку я новичок в Python и хочу знать, почему это плохо.
Это плохо не только потому, что это плохой питон, но и потому, что это вообще плохой код. Что такое fl? Определение цикла for слишком сложное. Путь жестко запрограммирован (и, например, не имеет значения). Он использует обескураженный питон (__import__), для чего нужен весь этот fl[i]? Это в основном нечитабельно и излишне сложно для чего-то, что не так уж сложно - см. Верхний голосовой ответ с его однострочным текстом.
Если вы хотите, чтобы это было у ваших местных жителей:
>>> 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()
документация для встроенной функции locals() явно предупреждает, что «содержимое этого словаря не должно изменяться».
Рекомендуемый способ для Python 2.7 и 3.1 и новее - использовать модуль importlib:
importlib.import_module(name, package=None)
Import a module. The name argument specifies what module to import in absolute or relative terms (e.g. either pkg.mod or ..mod). If the name is specified in relative terms, then the package argument must be set to the name of the package which is to act as the anchor for resolving the package name (e.g. import_module('..mod', 'pkg.subpkg') will import pkg.mod).
например
my_module = importlib.import_module('os.path')
Какой источник или авторитетный источник рекомендует?
Документация рекомендует против использовать функцию __import__ в пользу вышеупомянутого модуля.
Это хорошо работает для импорта os.path; как насчет from os.path import *?
Я получаю здесь ответ stackoverflow.com/a/44492879/248616 т.е. звонок globals().update(my_module.__dict)
@NamGVU, это опасный метод, поскольку он загрязняет ваши глобальные объекты и может переопределить любые идентификаторы с такими же именами. В опубликованной вами ссылке содержится лучшая, улучшенная версия этого кода.
дополнительный вопрос, связанный с @NamGVU: что возвращает importlib.import_module? В то время как «from my_module import func» выдает ошибку «Нет модуля с именем 'my_module'», my_module.func () работает. Как понять, что такое my_module?
Переменная my_module относится к typemodule, к тому же типу, что и результат import os.path.
Подобно решению @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>'
Например, мои имена модулей выглядят как jan_module / feb_module / mar_module.
month = 'feb'
exec 'from %s_module import *'%(month)
В настоящее время вы должны использовать 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)
Таким образом, вы можете получить доступ к членам (например, функции «hello») из вашего модуля pluginX.py - в этом фрагменте кода называется module - в его пространстве имен; Например, module.hello().
Если вы хотите импортировать элементы (например, «hello»), вы можете включить module / pluginX в список модулей в памяти:
sys.modules[module_name] = module
from pluginX import hello
hello()
Импорт пакета (например, pluginX/__init__.py) в текущем каталоге на самом деле прост:
import importlib
pkg = importlib.import_module('pluginX')
# check if it's all there..
def bla(mod):
print(dir(mod))
bla(pkg)
добавьте sys.modules[module_name] = module в конец вашего кода, если вы хотите иметь возможность import module_name впоследствии
@DanielBraun при этом выдает ошибку> ModuleNotFoundError
Что делает fromlist = ["myapp.commands"]?