Python `pkgutil.get_data` 扰乱了未来的进口

Python `pkgutil.get_data` disrupts future imports

考虑以下包结构:

.
├── module
│   ├── __init__.py
│   └── submodule
│       ├── attribute.py
│       ├── data.txt
│       └── __init__.py
└── test.py

和下面的一段代码:

import pkgutil
data = pkgutil.get_data('module.submodule', 'data.txt')
import module.submodule.attribute
retval = module.submodule.attribute.hello()

运行 这将引发错误:

Traceback (most recent call last):
  File "test.py", line 7, in <module>
    retval = module.submodule.attribute.hello()
AttributeError: module 'module' has no attribute 'submodule'

但是,如果您运行以下内容:

import pkgutil
import module.submodule.attribute
data = pkgutil.get_data('module.submodule', 'data.txt')
retval = module.submodule.attribute.hello()

import pkgutil
import module.submodule.attribute
retval = module.submodule.attribute.hello()

它工作正常。

为什么 运行ning pkgutil.get_data 会中断未来的导入?

首先,这是一个很好的问题,也是学习有关 python 的导入系统的新知识的好机会。那么让我们开始吧!

如果我们查看 the implementation of pkgutil.get_data,我们会看到类似这样的内容:

def get_data(package, resource):
    spec = importlib.util.find_spec(package)
    if spec is None:
        return None
    loader = spec.loader
    if loader is None or not hasattr(loader, 'get_data'):
        return None
    # XXX needs test
    mod = (sys.modules.get(package) or
           importlib._bootstrap._load(spec))
    if mod is None or not hasattr(mod, '__file__'):
        return None

    # Modify the resource name to be compatible with the loader.get_data
    # signature - an os.path format "filename" starting with the dirname of
    # the package's __file__
    parts = resource.split('/')
    parts.insert(0, os.path.dirname(mod.__file__))
    resource_name = os.path.join(*parts)
    return loader.get_data(resource_name)

你的问题的答案在这部分代码中:

    mod = (sys.modules.get(package) or
           importlib._bootstrap._load(spec))

它查看已经加载的包,如果我们正在寻找的包(本例中的 module.submodule)存在,它会使用它,如果不存在,则尝试使用 [=15= 加载包].

所以让我们看看 implementation of importlib._bootstrap._load 看看发生了什么。

def _load(spec):
    """Return a new module object, loaded by the spec's loader.
    The module is not added to its parent.
    If a module is already in sys.modules, that existing module gets
    clobbered.
    """
    with _ModuleLockManager(spec.name):
        return _load_unlocked(spec)

嗯,就在那里!文档说“该模块未添加到其 parent。”

表示加载了submodule模块,但没有添加到module模块中。因此,当我们尝试通过 module 访问 submodule 时,没有连接,因此 AtrributeError.

get_data 方法使用这个函数是有意义的,因为它只需要包中的一些其他文件,不需要导入整个包并将其添加到它的 parent及其 parents' parent 等等。

要亲自查看,我建议使用调试器并设置一些断点。然后你就可以一步一步地看到发生了什么。