关于python:如何导入所有子模块?

How to import all submodules?

我有如下目录结构:

1
2
3
4
5
6
| main.py
| scripts
|--| __init__.py
   | script1.py
   | script2.py
   | script3.py

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

编辑:很抱歉我有点含糊不清。我希望在运行时导入子模块,而不在__init__.py中明确指定它们。我可以用pkgutils.walk_packages来获得子模块的名称(除非有人知道更好的方法),但我不确定使用这些名称的最干净的方法(或者可能是walk_packages返回的进口商?)导入它们。


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

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

1
2
3
4
5
6
7
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],那么这是一种方法。

也许还有更好的办法,但这是我能做的最好的办法。

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

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

1
2
3
4
import script1
import script2
import script3
__all__ = ['script1', 'script2', 'script3']

您甚至可以不定义__all__,但如果您定义它,即使它只是一个导入内容的列表,事情(pydoc,如果没有其他东西的话)也会更清晰地工作。


这是基于Kolypto提供的答案,但他的答案不执行包的递归导入,而这确实执行了。虽然主要问题不需要,但我相信递归导入适用,并且在许多类似的情况下非常有用。例如,我在搜索这个主题时发现了这个问题。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

用途:

1
2
3
4
5
6
# from main.py, as per the OP's project structure
import scripts
import_submodules(scripts)

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


工作简单,允许在包内进行相对导入:

1
2
3
4
5
6
7
8
9
10
11
12
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__)
    }

用途:

1
__all__ = import_submodules(__name__).keys()

虽然没有我想要的那么干净,但是所有的清洁方法对我都没有效果。这将实现指定的行为:

目录结构:

1
2
3
4
5
6
7
8
| pkg
|--| __init__.py
   | main.py
   | scripts
   |--| __init__.py
      | script1.py
      | script2.py
      | script3.py

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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__)

虽然它很乱,但应该是便携式的。我已经为几个不同的包使用了这个代码。


我自己也厌倦了这个问题,所以我写了一个叫做automodinit的包来解决它。您可以从http://pypi.python.org/pypi/automodinit/获得它。用法如下:

  • 将automodinit包包含到您的setup.py依赖项中。
  • __init__.py文件的开头添加以下内容:
  • 1
    2
    3
    4
    5
    6
    __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]文件列表,还将导入每个文件在这些文件中,您好像键入了:

    1
    for x in __all__: import x

    因此,from M import *的效果与import M完全吻合。

    automodinit很高兴从zip文件中运行,因此是zip安全的。


    我一直在玩弄乔·金顿的答案,并建立了一个使用globalsget/setattr的解决方案,因此不需要eval。稍微修改一下,我使用packages父目录,然后只导入从__name__ +"."开始的模块,而不是直接使用__path__for walk_packages包。这样做是为了可靠地从walk_packages获取所有子包——在我的用例中,我有一个名为test的子包,它导致pkgutil从python的库中迭代test包;此外,使用__path__不会递归到包的子目录中。所有这些问题都是使用Jython和python2.5观察到的,下面的代码目前只在Jython中进行测试。

    还要注意,ops问题只讨论从包中导入所有模块,此代码也递归地导入所有包。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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__钩子使其具有动态性,因此实际的模块只有在访问时才被导入…


    在python 3中,可以将以下代码放入scripts.__init__.py文件中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import os
    import os.path as op

    __all__ = [
        op.splitext(f)[0]  # remove .py extension
        for f in os.listdir(BASE_DIR)  # list contents of current dir
        if not f.startswith('_') and
        ((op.isfile(op.join(BASE_DIR, f)) and f.endswith('.py')) or
         (op.isdir(op.join(BASE_DIR, f)) and op.isfile(op.join(BASE_DIR, f, '__init__.py'))))
    ]

    from . import *  # to make `scripts.script1` work after `import script`

    关于python导入的更多信息,我推荐david beazley在pycon 2015上的演讲:https://youtu.be/0oth1cxraq0


    在python 3.3中,这对我很有用。注意,这只适用于与__init__.py在同一目录下的文件中的子模块。但是,通过一些工作,它也可以增强以支持目录中的子模块。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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


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

    我知道这可能不是你想要的答案,但它为我实现了它的目标,对其他人也可能有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #!/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