【问题标题】:Dynamic loading of python modulespython模块的动态加载
【发布时间】:2010-10-31 08:52:20
【问题描述】:

在 python 中,如何在程序运行时将模块动态添加到包中。

我希望能够从外部进程将模块添加到包目录,并能够在我的程序中使用这些新模块:

import package

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

我该怎么做?

【问题讨论】:

标签: python dynamic python-import


【解决方案1】:

您的代码几乎是正确的。

__import__函数。

def doSomething(name):
    name = "package." + name
    mod = __import__(name, fromlist=[''])
    mod.doSomething()

【讨论】:

  • import 返回模块,因此您可以执行“mod = __import__(name)”,而不必在 sys.modules 中查找。
  • "当name变量为package.module形式时,通常返回顶层包(直到第一个点的名称),而不是name命名的模块。"跨度>
  • 对 Brian 评论的澄清... import 返回的是包,而不是模块。因此,您需要在 sys.modules 中查找模块或使用我的技术(在另一个答案中列出)从包中获取模块。
  • 另外,看起来 sys.modules 包含完全限定的模块名称。因此,您需要在 sys.modules 中查找 'package.'+name。
  • 编辑为使用 fromlist 参数。在 fromlist 中传递一些东西(任何东西)会导致 import 直接返回你想要的模块。
【解决方案2】:

Bastien 已经回答了这个问题,无论如何你可能会发现这个函数很有用,我用这个函数从字典的子文件夹中加载所有模块:

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

显然,您可以将所有“服务”子文件夹引用替换为参数。

【讨论】:

  • 关于“import 加载包而不是模块”的问题(我无法评论巴斯蒂安的回答):实际上 import 加载模块而不是使用 fromlist 参数时的包,即使是 [] (检查我的函数)
  • loadModules() 还返回 *.pyc 重要吗?
  • uhm help for os.path.exists() 没有说明部分匹配,我觉得它应该返回 *.pyc 文件。无论如何,在这种情况下,我认为这并不重要。
【解决方案3】:
import importlib

module = importlib.import_module('my_package.my_module')
my_class = getattr(module, 'MyClass')
my_instance = my_class()

【讨论】:

    【解决方案4】:

    Bastien 回答的一个技巧...__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()
    

    【讨论】:

    • 请注意,这不是“我的”答案,我只是在 S. Lott 的答案中添加了一些格式。
    【解决方案5】:

    要检测对目录的更改,在 Linux 上,您可以使用 pyinotifyhere 是一个很好的工作示例);在 Mac 上,fsevents(通过 Mac 附带的 PyObjC 包);在 Windows 上,Directory Change Notifications 通过 win32api(或 Python 标准库 ctypes 模块)。 AFAIK,没有人将这些不同的方法打包成一个便携包。 (当然,在最坏的情况下,您可以回退到“低技术”方法,例如定期轮询,如 Tim Golden's article,也许通过信号等触摸“来自外部进程的警报”。

    一旦您获得通知和新模块或修改模块的名称,您在问题中显示的代码应该可以工作。

    【讨论】:

      【解决方案6】:

      将模块目录添加到sys.path 并使用正常的import 语句。

      【讨论】:

      • 这并不能解决问题。模块名称由用户指定,正常导入似乎不需要字符串。此外,它不会检测到“新”模块。
      猜你喜欢
      • 2010-10-20
      • 2011-05-13
      • 2011-01-11
      • 2013-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多