【问题标题】:How to load modules dynamically on package import?如何在包导入时动态加载模块?
【发布时间】:2011-01-01 09:31:42
【问题描述】:

给定以下示例布局:

test/
  test.py
  formats/
    __init__.py
    format_a.py
    format_b.py

我尝试归档的是,每当我 import formats 时,__init__.pyformats 子目录中查找所有可用模块,加载它们并使它们可用(现在只需通过变量 supported_formats )。如果有更好、更 Python 或其他方式在运行时根据物理可用文件动态加载内容,请告知。

我的方法

我尝试过这样的事情(__init__.py):

supported_formats =  [__import__(f[:f.index('.py')]) for f in glob.glob('*.py')]

到目前为止,当我从命令行(从格式子目录或从其他目录)运行 __init__.py 时,我只是让它工作。但是当我从test.py 导入它时,它会像这样让我失望:

ImportError: No module named format_a.py

当我从 python 解释器导入它时,当我在除 formats 子目录之外的其他目录中启动解释器时也是如此。

这是整个代码。它还查找一个特定的类并将每个类的一个实例存储在一个字典中,但动态加载模块是我没有得到的主要部分:

def dload(get_cls=True, get_mod=True, key=None, fstring_mod='*.py', fstring_class=''):
  if p.dirname(__file__):
    path = p.split(p.abspath(__file__))[0]
    fstring_mod = p.join(path, fstring_mod)
    print >> sys.stderr, 'Path-Glob:', fstring_mod
  modules = [p.split(fn)[1][:fn.index('.py')] for fn in glob.glob(fstring_mod)]
  print >> sys.stderr, 'Modules:', ', '.join(modules)
  modules = [__import__(m) for m in modules]
  if get_cls:
    classes = {} if key else []
    for m in modules:
      print >> sys.stderr, "-", m
      for c in [m.__dict__[c]() for c in m.__dict__ if c.startswith(fstring_class)]:
        print >> sys.stderr, " ", c
        if key:
          classes[getattr(c, key)] = c
        else:
          classes.append(c)
    if get_mod:
      return (modules, classes)
    else:
      return classes
  elif get_mod:
    return modules

_supported_formats = dload(get_mod=False, key='fid', fstring_mod='format_*.py', fstring_class='Format')

我的想法

文件系统路径之类的东西可能很混乱。我想用模块命名空间或类似的东西来处理这个问题,但我现在有点迷失如何开始和如何寻址模块,所以它们可以从任何地方访问。

【问题讨论】:

    标签: python dynamic import packaging


    【解决方案1】:

    您需要对代码进行两个修复:

    1. 您应该调用__import__(m, globals(), locals()) 而不是__import__(m)。这是 Python 在包中定位模块所必需的。

    2. 您的代码没有正确删除 .py 扩展名,因为您在错误的字符串上调用 index()。如果它始终是 .py 扩展名,您可以简单地使用 p.split(fn)[1][:-3]

    【讨论】:

      【解决方案2】:

      首先,您必须确保您的代码无论当前工作目录如何都能正常工作。为此,您使用 __file__ 变量。您还应该使用绝对导入。

      类似(未经测试):

      supported_formats = {}
      for fn in os.listdir(os.path.dirname(__file__)):
          if fn.endswith('.py'):
              exec ("from formats import %s" % fn[:-3]) in supported_formats
      

      【讨论】:

      • 我做了类似的事情:我在用于glob() 的文件掩码之前附加了dirname。找到的文件但我无法导入它们,我想是因为我在调用__import__ 时没有使用globals()locals
      • 子包像“import formats.format_a”一样被导入,所以也许你必须在导入语句前加上“formats”。 ?
      【解决方案3】:

      在 sys.path 中搜索模块。您应该能够使用模块的路径扩展 sys.path。我也不确定您是否可以使用“module.py”约定在 sys.path 上加载模块,我认为最好不使用“.py”。

      这显然不是一个解决方案,但仍然可能很方便。

      【讨论】:

      • formats 子目录实际上已经在sys.path 上,当我从test.py 执行import formats 时,我无法从__init__.py 导入它下的文件。可能是因为我在对__import__ 的调用中没有包含globals()
      【解决方案4】:

      我想如果你做了什么,“格式”将是你的包,所以当你告诉它import formats 你应该能够访问该包中的其余模块,所以,你会有类似 @ 987654322@

      不确定,我只是个 n00b。

      【讨论】:

      • 你是对的。但我希望 Python 在运行时为我进行抓取,因为我事先不知道 formats/ 目录中文件的名称和数量。
      【解决方案5】:

      这是我在 interjay 更正后提出的代码。仍然不确定这是否是好的风格。

      def load_modules(filemask='*.py', ignore_list=('__init__.py', )):
        modules = {}
        dirname = os.path.dirname(__file__)
        if dirname:
          filemask = os.path.join(dirname, filemask)
        for fn in glob.glob(filemask):
          fn = os.path.split(fn)[1]
          if fn in ignore_list:
            continue
          fn = os.path.splitext(fn)[0]
          modules[fn] = __import__(fn, globals(), locals())
        return modules
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-03-11
        • 2017-07-13
        • 1970-01-01
        • 1970-01-01
        • 2020-10-17
        • 2011-04-04
        • 2019-05-18
        相关资源
        最近更新 更多