python 如何导入所有子模块?

js81xvg6  于 2023-06-28  发布在  Python
关注(0)|答案(9)|浏览(119)

我有一个目录结构如下:

| 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返回的Importors?)来导入它们。

yrefmtwq

yrefmtwq1#

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

(顶部软件包目录中__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)也会更干净地工作。

vulvrdjw

vulvrdjw2#

这是基于the answer that kolypto provided,但他的答案不执行包的递归导入,而这是。虽然主问题没有要求,但我相信递归导入适用,并且在许多类似的情况下非常有用。我是在搜索这个主题时发现这个问题的。
这是一种很好的、干净的子包模块导入方式,并且应该是可移植的,它使用了python2.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]
    """

try:results[full_name] = importlib.import_module(full_name)and except ModuleNotFoundError:继续

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

osh3o9ms3#

简单地工作,并允许在包内相对导入:

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

jtw3ybtb4#

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

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

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

yruzcnhs

yruzcnhs5#

我自己也厌倦了这个问题,所以我写了一个名为automodinit的包来解决它。你可以从http://pypi.python.org/pypi/automodinit/得到它。用法是这样的:
1.将automodinit包包含到setup.py依赖项中。
1.将以下内容添加到__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安全的。

ua4mk5z4

ua4mk5z46#

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

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

gjmwrych7#

我正在编写一个小型的个人库,并一直在添加新的模块,所以我编写了一个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 '\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
zed5wv10

zed5wv108#

我尝试过Joe Kington's Answer,并构建了一个使用globalsget/setattr的解决方案,因此不需要eval。一个小小的修改是,我没有直接使用walk_packages的包__path__,而是使用包的父目录,然后只导入以__name__ + "."开头的模块。这样做是为了可靠地从walk_packages中获取所有子包-在我的用例中,我有一个名为test的子包,这导致pkgutil从python的库中迭代test包;此外,使用__path__不会递归到packages子目录。所有这些问题都是使用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__钩子来使其动态化,这样实际的模块只有在访问时才被导入……

x6492ojm

x6492ojm9#

这在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

相关问题