如何导入所有子模块?

2025-03-12 08:55:00
admin
原创
10
摘要:问题描述:我有一个如下的目录结构:| main.py | scripts |--| __init__.py | script1.py | script2.py | script3.py 在 中main.py,如果我import scripts,这显然不允许我使用scripts.script1...

问题描述:

我有一个如下的目录结构:

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

在 中main.py,如果我import scripts,这显然不允许我使用scripts.script1。我知道我可以使用from scripts import *来访问包中的模块scripts,但是我只能直接将它们用作等scripts1scripts2

我该如何编写代码以便可以参考scripts.script1里面的内容main.py

我尝试使用pkgutils.walk_packages以及__all__包的属性来获取子模块名称,但我无法找到使用这些字符串进行导入的方法。


解决方案 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,如果没有别的)将会更干净地工作,即使它只是您导入内容的列表。

解决方案 2:

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

这是一种很好的、​​干净的执行子包模块导入的方法,并且也应该是可移植的,它使用 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
        try:
            results[full_name] = importlib.import_module(full_name)
        except ModuleNotFoundError:
            continue
        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__)

解决方案 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:

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

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)

解决方案 5:

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

目录结构:

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

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

解决方案 6:

我自己也厌倦了这个问题,所以我写了一个名为 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 安全的。

解决方案 7:

为了确保子模块不会加载到不同于的位置module.__path__,您可以使用这种方法:

def import_submodules(module, recursive=False):
    """Import all submodules of a module, recursively."""
    from sys import modules
    from pkgutil import walk_packages
    from importlib.util import module_from_spec
    module_stack = [walk_packages(
        module.__path__,
        module.__name__ + '.')
    ]
    while module_stack:
        gen = module_stack.pop()
        for loader, module_name, is_pkg in gen:
            _spec = loader.find_spec(module_name)
            _module = module_from_spec(_spec)
            _spec.loader.exec_module(_module)
            modules[module_name] = _module
            yield _module
            if recursive:
                module_stack.append(
                     walk_packages(
                         _module.__path__,
                         _module.__name__ + '.'
                     )
                )

解决方案 8:

我正在编写一个小型个人库,并一直在添加新模块,因此我编写了一个 shell 脚本来查找脚本并创建__init__.py。该脚本在我的包 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 '    ->    ' 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 '    ->    ' 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 "    "$0 }'
done

解决方案 9:

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

还要注意,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__包上的钩子使其动态化,因此实际的模块仅在被访问时才会被导入......

解决方案 10:

在 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
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1579  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1355  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   8  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   9  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用