【问题标题】:How to import all submodules?如何导入所有子模块?
【发布时间】:2011-03-22 21:34:42
【问题描述】:

我的目录结构如下:

| main.py
| scripts
|--| __init__.py
   | script1.py
   | script2.py
   | script3.py

main.py 导入模块scripts。我尝试将pkgutils.walk_packages__all__ 结合使用,但使用它,我只能使用from scripts import * 直接导入main 下的所有子模块。我想把它们都放在scripts 下。导入scripts 的所有子模块以便我可以从main 访问scripts.script1 的最干净的方法是什么?

编辑:对不起,我有点含糊。我想在运行时导入子模块而不在__init__.py 中明确指定它们。我可以使用pkgutils.walk_packages 来获取子模块名称(除非有人知道更好的方法),但我不确定使用这些名称(或者可能是walk_packages 返回的 ImpImporters?)的最简洁方式来导入它们。

【问题讨论】:

  • 如果您将应用程序部署为压缩鸡蛋,pkgutil 会工作吗?你可以看看“import pkg_resources”,以防万一
  • 我对这个问题和答案感到非常困惑。为什么这完全需要。我的理解是,您有两个选项可以在 python 中包含所有本地“模块”(其他语言仅称为文件。a)您在每个文件夹中都包含一个空白初始化文件,并且 ALONE 告诉 python 这是一个模块文件夹并在运行之前将其全部导入或 b) 为避免到处都有 init 文件,您在文件顶部放置一个序言以将所有内容添加到路径中。还有其他注意事项,例如 IDE 中的代码导航。

标签: python import module


【解决方案1】:

编辑:这是在运行时递归导入所有内容的一种方法...

(顶部包目录中__init__.py的内容)

import pkgutil

__all__ = []
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
    __all__.append(module_name)
    _module = loader.find_module(module_name).load_module(module_name)
    globals()[module_name] = _module

我在这里没有使用__import__(__path__+'.'+module_name),因为使用它很难正确递归地导入包。但是,如果您没有嵌套的子包,并且想避免使用globals()[module_name],这是一种方法。

可能有更好的方法,但无论如何,这是我能做的最好的了。

原始答案(对于上下文,请忽略。我最初误解了这个问题):

你的scripts/__init__.py 是什么样的?它应该是这样的:

import script1
import script2
import script3
__all__ = ['script1', 'script2', 'script3']

你甚至可以不定义__all__,但是如果你定义它,事情(pydoc,如果没有别的)会更干净,即使它只是你导入的内容的列表。

【讨论】:

  • 抱歉没有充分解释。我已经编辑了主要帖子。我希望在运行时动态导入子模块。
  • 使用 exec() 太过分了——你可以将加载的模块插入 globals() 并解决问题。
  • 由于某种原因,当我的模块尝试导入包中的其他模块时,这会失败。
  • 这只是我还是不适用于相对进口?如果我有script1/file1.pyfrom .. import script2 行,我会收到错误ValueError: attempted relative import beyond top-level package
  • +1 给那些报告它与包中的相对导入不兼容的人。我面临同样的问题。对我来说,当我的子模块之一执行 from .blah import Blah 时,它会显示 SystemError: Parent module '' not loaded, cannot perform relative import
【解决方案2】:

这是基于the answer that kolypto provided,但他的回答不执行包的递归导入,而这样做。虽然主要问题不需要,但我相信递归导入适用并且在许多类似情况下非常有用。例如,我在搜索该主题时发现了这个问题。

这是执行子包模块导入的一种很好、干净的方式,并且应该是可移植的,它使用 python 2.7+ / 3.x 的标准库。

import importlib
import pkgutil


def import_submodules(package, recursive=True):
    """ Import all submodules of a module, recursively, including subpackages

    :param package: package (name or actual module)
    :type package: str | module
    :rtype: dict[str, types.ModuleType]
    """
    if isinstance(package, str):
        package = importlib.import_module(package)
    results = {}
    for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
        full_name = package.__name__ + '.' + name
        results[full_name] = importlib.import_module(full_name)
        if recursive and is_pkg:
            results.update(import_submodules(full_name))
    return results

用法:

# from main.py, as per the OP's project structure
import scripts
import_submodules(scripts)

# Alternatively, from scripts.__init__.py
import_submodules(__name__)

【讨论】:

  • 我相信pkgutil.walk_packages() 本身就是递归的,所以你的函数不需要递归。
  • pkgutil.walk_packages() 确实声称。但是,它并没有这样做。只需在某个包树上运行它,使用recursive=Truerecursive=False。你会看到区别。
【解决方案3】:

简单有效,并允许在包内进行相对导入:

def import_submodules(package_name):
    """ Import all submodules of a module, recursively

    :param package_name: Package name
    :type package_name: str
    :rtype: dict[types.ModuleType]
    """
    package = sys.modules[package_name]
    return {
        name: importlib.import_module(package_name + '.' + name)
        for loader, name, is_pkg in pkgutil.walk_packages(package.__path__)
    }

用法:

__all__ = import_submodules(__name__).keys()

【讨论】:

    【解决方案4】:

    没有我想要的那么干净,但没有一种更清洁的方法对我有用。这实现了指定的行为:

    目录结构:

    | pkg
    |--| __init__.py
       | main.py
       | scripts
       |--| __init__.py
          | script1.py
          | script2.py
          | script3.py
    

    其中pkg/scripts/__init__.py 为空,pkg/__init__.py 包含:

    import importlib as _importlib
    import pkgutil as _pkgutil
    __all__ = [_mod[1].split(".")[-1] for _mod in
               filter(lambda _mod: _mod[1].count(".") == 1 and not 
                                   _mod[2] and __name__ in _mod[1],
                      [_mod for _mod in _pkgutil.walk_packages("." + __name__)])]
    __sub_mods__ = [".".join(_mod[1].split(".")[1:]) for _mod in
                    filter(lambda _mod: _mod[1].count(".") > 1 and not 
                                        _mod[2] and __name__ in _mod[1],
                           [_mod for _mod in 
                            _pkgutil.walk_packages("." + __name__)])]
    from . import *
    for _module in __sub_mods__:
        _importlib.import_module("." + _module, package=__name__)
    

    虽然杂乱无章,但它应该是可移植的。我已经将此代码用于几个不同的包。

    【讨论】:

    • :-|只是好奇——清洁方法不起作用怎么办?
    • Tbh,我不记得了.. 对不起!该限制甚至可能不再存在/适用,因此可能值得一试。我不确定它是否解决了这个问题,但现在我使用__all__ = [_mod[1] for _mod in _pkgutil.iter_modules(__path__) if not _mod[2]]。我很确定 iter_modules 函数当时不存在。
    【解决方案5】:

    我自己也厌倦了这个问题,所以我写了一个名为 automodinit 的包来修复它。您可以从http://pypi.python.org/pypi/automodinit/ 获得它。用法是这样的:

    1. 将 automodinit 包包含到您的 setup.py 依赖项中。
    2. 将以下内容添加到__init__.py 文件的开头:
    __all__ = ["I will get rewritten"]
    # Don't modify the line above, or this line!
    import automodinit
    automodinit.automodinit(__name__, __file__, globals())
    del automodinit
    # Anything else you want can go after here, it won't get modified.
    

    就是这样!从现在开始导入模块会将__all__ 设置为 模块中的 .py[co] 文件列表,还将导入每个 这些文件中的内容,就像您输入的一样:

    for x in __all__: import x
    

    因此from M import * 的效果与import M 完全匹配。

    automodinit 很乐意从 ZIP 存档内部运行,因此是 ZIP 安全的。

    【讨论】:

      【解决方案6】:

      加载一个包的所有子模块,你可以使用这个简单的函数:

      import importlib
      import pkgutil
      
      def import_submodules(module):
          """Import all submodules of a module, recursively."""
          for loader, module_name, is_pkg in pkgutil.walk_packages(
                  module.__path__, module.__name__ + '.'):
              importlib.import_module(module_name)
      

      用例:加载 Flask 应用程序的所有数据库模型,以便 Flask-Migrate 可以检测到架构的更改。用法:

      import myproject.models
      import_submodules(myproject.models)
      

      【讨论】:

        【解决方案7】:

        我一直在编写一个小型个人库并添加新模块,因此我编写了一个 shell 脚本来查找脚本并创建 __init__.py's。该脚本在我的包 pylux 的主目录之外执行。

        我知道这可能不是您要寻找的答案,但它为我服务,它可能对其他人也有用。

        #!/bin/bash
        
        echo 'Traversing folder hierarchy...'
        
        CWD=`pwd`
        
        
        for directory in `find pylux -type d -exec echo {} \;`;
        do
            cd $directory
            #echo Entering $directory
            echo -n "" > __init__.py
        
            for subdirectory in `find . -type d -maxdepth 1 -mindepth 1`;
            do
                subdirectory=`echo $subdirectory | cut -b 3-`
                #echo -n '    ' ...$subdirectory
                #echo -e '\t->\t' import $subdirectory
                echo import $subdirectory >> __init__.py
            done
        
            for pyfile in *.py ;
            do
                if [ $pyfile = $(echo __init__.py) ]; then
                    continue
                fi
                #echo -n '    ' ...$pyfile
                #echo -e '\t->\t' import `echo $pyfile | cut -d . -f 1`
                echo import `echo $pyfile | cut -d . -f 1` >> __init__.py
            done
            cd $CWD
        
        done
        
        
        for directory in `find pylux -type d -exec echo {} \;`;
        do
            echo $directory/__init__.py:
            cat $directory/__init__.py | awk '{ print "\t"$0 }'
        done
        

        【讨论】:

        • 不能用__init__.py写同样的逻辑吗?使用os.listdir__import__?
        • 嗯。我想是的,但我得再研究一下。
        【解决方案8】:

        我玩过Joe Kington's Answer 并构建了一个使用globalsget/setattr 的解决方案,因此不需要评估。稍微修改一下,我没有直接使用包__path__walk_packages,而是使用包父目录,然后只导入以__name__ + "." 开头的模块。这样做是为了可靠地从 walk_packages 获取所有子包 - 在我的用例中,我有一个名为 test 的子包,它导致 pkgutil 迭代 python 库中的 test 包;此外,使用__path__ 不会递归到包子目录中。所有这些问题都是使用 jython 和 python2.5 观察到的,下面的代码目前只在 jython 中测试过。

        还请注意,OP 问题仅涉及从包中导入所有 模块,此代码也递归地导入所有包。

        from pkgutil import walk_packages
        from os import path
        
        __all__ = []
        __pkg_prefix = "%s." % __name__
        __pkg_path = path.abspath(__path__[0]).rsplit("/", 1)[0] #parent directory
        
        for loader, modname, _ in walk_packages([__pkg_path]):
            if modname.startswith(__pkg_prefix):
                #load the module / package
                module = loader.find_module(modname).load_module(modname)
                modname = modname[len(__pkg_prefix):] #strip package prefix from name
                #append all toplevel modules and packages to __all__
                if not "." in modname:
                    __all__.append(modname)
                    globals()[modname] = module
                #set everything else as an attribute of their parent package
                else:
                    #get the toplevel package from globals()
                    pkg_name, rest = modname.split(".", 1)
                    pkg = globals()[pkg_name]
                    #recursively get the modules parent package via getattr
                    while "." in rest:
                        subpkg, rest = rest.split(".", 1)
                        pkg = getattr(pkg, subpkg)
                    #set the module (or package) as an attribute of its parent package
                    setattr(pkg, rest, module)
        

        作为未来的改进,我将尝试在包上使用 __getattr__ 钩子使其动态化,因此实际模块仅在访问时才会导入...

        【讨论】:

          【解决方案9】:

          这在 Python 3.3 中非常适合我。请注意,这仅适用于与__init__.py 位于同一目录中的文件中的子模块。但是,通过一些工作,它也可以增强以支持目录中的子模块。

          from glob import iglob
          from os.path import basename, relpath, sep, splitext
          
          def import_submodules(__path__to_here):
              """Imports all submodules.
              Import this function in __init__.py and put this line to it:
              __all__ = import_submodules(__path__)"""
              result = []
              for smfile in iglob(relpath(__path__to_here[0]) + "/*.py"):
                  submodule = splitext(basename(smfile))[0]
                  importstr = ".".join(smfile.split(sep)[:-1])
                  if not submodule.startswith("_"):
                      __import__(importstr + "." + submodule)
                      result.append(submodule)
              return result
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-12-06
            • 1970-01-01
            • 2012-05-28
            • 2018-01-29
            • 2015-10-23
            • 1970-01-01
            • 2016-10-31
            • 1970-01-01
            相关资源
            最近更新 更多