python3中的pkgutil.walk_packages需要__init__.py吗?

时间:2016-12-17 22:40:33

标签: python python-3.x

PEP420使__init__.py个文件可选:https://docs.python.org/3/whatsnew/3.3.html#pep-420-implicit-namespace-packages

虽然似乎没有它们,pkgutil.walk_packages无法正常运行:https://docs.python.org/3/library/pkgutil.html#pkgutil.walk_packages

考虑以下示例:

$ tree foo
foo
├── bar
│   ├── baz.py
│   └── __init__.py
├── __init__.py
└── womp.py

和测试脚本

# test.py
import pkgutil

import foo


for _, mod, _ in pkgutil.walk_packages(foo.__path__, foo.__name__ + '.'):
    print(mod)

在python2和python3中,我得到以下输出:

$ python2.7 test.py
foo.bar
foo.bar.baz
foo.womp
$ python3.5 test.py
foo.bar
foo.bar.baz
foo.womp

删除__init__.py文件并仅使用python3,我得到了这个:

$ find -name '__init__.*' -delete
$ python3.5 test.py
foo.bar

模块绝对可以导入:

$ python3.5 -c 'import foo.bar.baz'
$

这是一个错误吗?我是否被迫创建__init__.py文件以实现我想要的目标?

2 个答案:

答案 0 :(得分:1)

作为一种解决方法(也许这会帮助其他人),我使用的是这样的东西。它不是完美的(如果pwd发生变化或者包没有根据,那就破坏了。)但它确实做了我想要做的简单用例:

def walk_modules(pkg):
    assert hasattr(pkg, '__path__'), 'This function is for packages'
    path = pkg.__name__.replace('.', '/')
    modules = []
    for root, _, filenames in os.walk(path):
        for filename in filenames:
            if filename.startswith('.') or not filename.endswith('.py'):
                continue
            path = os.path.join(root, filename)
            modules.append(os.path.splitext(path)[0].replace('/', '.'))
    for module in sorted(modules):
        yield __import__(module, fromlist=['__trash'])

答案 1 :(得分:1)

另一种方法尊重合并的名称空间包的__path__属性:

import pkgutil
from pathlib import Path

def iter_packages(path, prefix, onerror=None):
    """ Find packages recursively, including PEP420 packages """
    yield from pkgutil.walk_packages(path, prefix, onerror)
    namespace_packages = {}
    for path_root in path:
        for sub_path in Path(path_root).iterdir():
            # TODO: filter to legal package names
            if sub_path.is_dir() and not (sub_path / '__init__.py').exists():
                ns_paths = namespace_packages.setdefault(prefix + sub_path.name, [])
                ns_paths.append(str(sub_path))
    for name, paths in namespace_packages.items():
        # TODO: construct a loader somehow?
        yield pkgutil.ModuleInfo(None, name, True)
        yield from iter_packages(paths, name + '.', onerror)