Динамическая загрузка модулей Python

В python, как вы динамически добавляете модули в пакет во время работы вашей программы.

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

import package

def doSomething(name):
    pkg = __import__("package." + name)
    mod = getattr(pkg, name)
    mod.doSomething()

Как мне это сделать?


person Community    schedule 04.06.2009    source источник
comment
возможный дубликат динамической загрузки модулей Python   -  person Björn Pollex    schedule 02.09.2011
comment
Возможный дубликат импорта динамического модуля в Python   -  person scai    schedule 22.11.2017


Ответы (5)


Ваш код почти правильный.

См. Функцию __import__.

def doSomething(name):
    name = "package." + name
    mod = __import__(name, fromlist=[''])
    mod.doSomething()
person Community    schedule 04.06.2009
comment
import возвращает модуль, поэтому вы можете сделать mod = __import __ (name) вместо того, чтобы искать его в sys.modules. - person Brian; 04.06.2009
comment
Когда переменная name имеет вид package.module, обычно возвращается пакет верхнего уровня (имя до первой точки), а не модуль, названный по имени. - person S.Lott; 04.06.2009
comment
Пояснение к комментарию Брайана ... import возвращает пакет, а не модуль. Итак, вам нужно либо найти модуль в sys.modules, либо использовать мою технику (указанную в другом ответе), чтобы получить модуль из пакета. - person Clint Miller; 04.06.2009
comment
Кроме того, похоже, что sys.modules содержит полное имя модуля. Итак, вам нужно найти 'package.' + Name в sys.modules. - person Clint Miller; 04.06.2009
comment
Отредактировано для использования аргумента fromlist. Передача чего-либо (чего угодно) в fromlist приводит к тому, что import возвращает нужный модуль напрямую. - person Carl Meyer; 05.06.2009
comment
Откатил одно изменение. Передача fromlist=[] имеет тот же эффект, что и полное отсутствие указания fromlist. Добавление пустой строки решает эту проблему. - person Andrejs Cainikovs; 10.07.2014

Бастьен уже ответил на вопрос, в любом случае вам может пригодиться эта функция, которую я использую для загрузки всех модулей из подпапки в словаре:

def loadModules():
    res = {}
    import os
    # check subfolders
    lst = os.listdir("services")
    dir = []
    for d in lst:
        s = os.path.abspath("services") + os.sep + d
        if os.path.isdir(s) and os.path.exists(s + os.sep + "__init__.py"):
            dir.append(d)
    # load the modules
    for d in dir:
        res[d] = __import__("services." + d, fromlist = ["*"])
    return res

Другой - создать экземпляр объекта классом, определенным в одном из модулей, загруженных первой функцией:

def getClassByName(module, className):
    if not module:
        if className.startswith("services."):
            className = className.split("services.")[1]
        l = className.split(".")
        m = __services__[l[0]]
        return getClassByName(m, ".".join(l[1:]))
    elif "." in className:
        l = className.split(".")
        m = getattr(module, l[0])
        return getClassByName(m, ".".join(l[1:]))
    else:
        return getattr(module, className)

Вот простой способ использования этих функций:

mods = loadModules()
cls = getClassByName(mods["MyModule"], "submodule.filepy.Class")
obj = cls()

Очевидно, вы можете заменить все ссылки на подпапку "services" параметрами.

person Community    schedule 04.06.2009
comment
по поводу вопроса import загружайте пакет, а не модуль (я не могу комментировать ответ Бастьяна): на самом деле import загружает модуль вместо пакета, когда используется параметр fromlist, даже если [] (проверьте мою функцию) - person Giorgio Gelardi; 04.06.2009
comment
loadModules () также возвращает * .pyc, это важно? - person DrFalk3n; 12.06.2009
comment
uhm help для os.path.exists () ничего не говорит о частичном совпадении, я считаю, что он должен не возвращать файлы * .pyc. во всяком случае, в данном случае я думаю, что это не очень важно. - person Giorgio Gelardi; 12.06.2009

Один трюк с ответом Бастьена ... Функция __import__() возвращает объект пакета, а не объект модуля. Если вы используете следующую функцию, она динамически загрузит модуль из пакета и вернет вам модуль, а не пакет.

def my_import(name):
    mod = __import__(name)
    components = name.split('.')
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod

Тогда вы сможете:

mod = my_import('package.' + name)
mod.doSomething()
person Community    schedule 04.06.2009
comment
Обратите внимание, что это не мой ответ, я просто добавил немного форматирования к ответу С. Лотта. - person Bastien Léonard; 05.06.2009

Чтобы обнаружить изменения в каталоге в Linux, вы можете использовать pyinotify (здесь хороший рабочий пример); на Mac: fsevents (через пакет PyObjC, поставляемый с вашим Mac); в Windows: Уведомления об изменении каталога через win32api (или модуль стандартной библиотеки Python ctypes). AFAIK, никто не объединил эти различные подходы в один портативный пакет. (Конечно, в худшем случае вы можете вернуться к «более низким технологиям», таким как периодический опрос, например, статья Тима Голдена, возможно, с добавлением" предупреждений от внешнего процесса "через сигнал и т. д.).

Когда у вас есть уведомление и имя нового или измененного модуля, код, который вы показываете в вопросе, должен работать.

person Community    schedule 04.06.2009

Добавьте каталог модуля в sys.path и используйте обычный оператор import.

person Community    schedule 04.06.2009
comment
Это не решает проблемы. Имя модуля задается пользователем, и при обычном импорте строка не создается. Кроме того, он не обнаружит новые модули. - person ; 04.06.2009