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
文件以实现我想要的目标?
答案 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)