Как импортировать модуль с именем в виде строки?

Я пишу приложение 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()

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

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

Что делает fromlist = ["myapp.commands"]?

Pieter Müller 06.06.2012 14:29

@ PieterMüller: в оболочке Python введите следующее: dir(__import__). Fromlist должен быть списком имён для имитации «from name import ...».

mawimawi 09.08.2012 11:13

По состоянию на 2019 год вы должны искать importlib: stackoverflow.com/a/54956419/687896

Brandt 13.03.2019 12:42

Не используйте __import__, см. Документ Python, используйте importlib.import_module

fcm 28.06.2019 18:00
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
577
5
326 073
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

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

exec("import myapp.commands.%s" % command)

Как мне получить дескриптор модуля через exec, чтобы я мог вызвать (в этом примере) метод .run ()?

Kamil Kisiel 19.11.2008 09:19

Затем вы можете выполнить getattr (myapp.commands, command) для доступа к модулю.

Greg Hewgill 19.11.2008 09:20

... или добавьте as command_module в конец оператора импорта, а затем выполните command_module.run()

oxfn 16.10.2013 14:43

В некоторых версиях Python 3+ exec был преобразован в функцию с дополнительным преимуществом, заключающимся в том, что результирующая ссылка, созданная в источнике, сохраняется в аргументе locals () функции exec (). Чтобы еще больше изолировать exec'd, на который ссылается локальный блок кода, вы можете предоставить свой собственный dict, например пустой, и ссылаться на ссылки, используя этот dict, или передать этот dict другим функциям exec (source, gobals (), command1_dict) .... print (command1_dict ['somevarible'])

DevPlayer 08.04.2015 16:43
Ответ принят как подходящий

С 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?

user1767754 17.09.2014 11:10

Как вы могли использовать __init__ этого модуля?

Dorian Dore 25.03.2015 06:00

Одна проблема с этим решением для OP заключается в том, что перехват исключений для одного или двух плохих «командных» модулей приводит к отказу всего его / ее командного приложения при одном исключении. Лично я бы перебирал каждый импорт, индивидуально завернутый в try: mods = __ import __ () \ nexcept ImportError as error: report (error), чтобы позволить другим командам продолжать работать, пока плохие исправляются.

DevPlayer 08.04.2015 16:38

Другая проблема с этим решением, как указывает Денис Малиновский ниже, заключается в том, что сами документы python рекомендуют не использовать __import__. Документы 2.7: «Поскольку эта функция предназначена для использования интерпретатором Python, а не для общего использования, лучше использовать importlib.import_module () ...» При использовании python3 модуль imp решает эту проблему, как упоминает ниже монкут .

LiavK 08.07.2015 18:14

fyi, это забавное "злоупотребление" картой :) Интересно, почему в python нет такой вещи, как foreach, хотя

John Wu 13.11.2015 02:23

@JohnWu зачем вам foreach, когда у вас есть for element in и map() :)

cowbert 20.02.2018 01:59

Я решил сэкономить время всем, кто пытается использовать автозаполнение с динамическими модулями с PyCharm. Не работает.

Eduardo Pignatelli 04.09.2018 16:40

Обратите внимание, что карта НЕ будет работать в python 3, поскольку карта теперь использует ленивую оценку.

swhat 14.11.2018 00:52

Используйте модуль imp или более прямую функцию __import__().

Не используйте __import__, см. Документ Python, используйте importlib.import_module

fcm 28.06.2019 18:01

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? Почему бы не проверить сначала скомпилированную версию, а затем исходную. Ожидание класса снижает возможность повторного использования в вашем случае.

stamat 01.07.2013 00:40

В строке, где вы создаете MyClass в целевом модуле, вы добавляете избыточную ссылку на имя класса. Он уже хранится в expected_class, поэтому вы можете использовать class_inst = getattr(py_mod,expected_name)().

Andrew 16.10.2013 11:11
Обратите внимание, что если существует правильно соответствующий скомпилированный в байтах файл (с суффиксом .pyc или .pyo), он будет использоваться вместо анализа данного исходного файла.. https://docs.python.org/2/library/imp.html#imp.load_source
cdosborn 01.07.2015 00:07

Внимание: это решение работает; однако модуль imp будет устаревшим в пользу import lib, см. страницу imp: "" "Устарело с версии 3.4: пакет imp ожидает прекращения поддержки в пользу importlib." ""

リカルド 20.11.2016 12:50

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

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

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

Пожалуйста, не прячьте ответы без объяснения причин в комментариях. Это никому не помогает.

Rebs 11.06.2014 12:31

Я хотел бы знать, почему это плохо.

DevPlayer 01.09.2016 05:40

То же, что и я, поскольку я новичок в Python и хочу знать, почему это плохо.

Kosmo零 03.11.2016 20:50

Это плохо не только потому, что это плохой питон, но и потому, что это вообще плохой код. Что такое fl? Определение цикла for слишком сложное. Путь жестко запрограммирован (и, например, не имеет значения). Он использует обескураженный питон (__import__), для чего нужен весь этот fl[i]? Это в основном нечитабельно и излишне сложно для чего-то, что не так уж сложно - см. Верхний голосовой ответ с его однострочным текстом.

Phil 20.07.2017 08:07

Если вы хотите, чтобы это было у ваших местных жителей:

>>> 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() явно предупреждает, что «содержимое этого словаря не должно изменяться».

martineau 14.01.2020 19:58

Рекомендуемый способ для 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')

Какой источник или авторитетный источник рекомендует?

michuelnik 08.04.2015 16:21

Документация рекомендует против использовать функцию __import__ в пользу вышеупомянутого модуля.

Denis Malinovsky 09.04.2015 15:42

Это хорошо работает для импорта os.path; как насчет from os.path import *?

Nam G VU 12.06.2017 09:31

Я получаю здесь ответ stackoverflow.com/a/44492879/248616 т.е. звонок globals().update(my_module.__dict)

Nam G VU 12.06.2017 09:46

@NamGVU, это опасный метод, поскольку он загрязняет ваши глобальные объекты и может переопределить любые идентификаторы с такими же именами. В опубликованной вами ссылке содержится лучшая, улучшенная версия этого кода.

Denis Malinovsky 19.06.2017 20:11

дополнительный вопрос, связанный с @NamGVU: что возвращает importlib.import_module? В то время как «from my_module import func» выдает ошибку «Нет модуля с именем 'my_module'», my_module.func () работает. Как понять, что такое my_module?

Joonho Park 01.07.2020 11:41

Переменная my_module относится к typemodule, к тому же типу, что и результат import os.path.

Ioannis Filippidis 27.01.2021 18:03

Подобно решению @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 впоследствии

Daniel Braun 15.04.2019 09:38

@DanielBraun при этом выдает ошибку> ModuleNotFoundError

Nam G VU 09.06.2020 09:12

Другие вопросы по теме