即使在Python中清除了'sys.path',`import`语句是如何工作的?

4

我正在学习Python模块,据我理解,当我们在代码中尝试import一个模块时,Python会查找该模块是否存在于sys.path中,如果不存在,则会引发ModuleNotFoundError

sys.path添加位置

假设我想从一个默认不存在于sys.path中的位置进行导入,我可以简单地将这个新位置附加到sys.path中,如下面的片段所示,一切都可以正常工作。

~/Documents/python-modules/usemymodule.py

import sys
sys.path.append("/home/som/Documents/modules")

import mymodule
mymodule.yell("Hello World")

~/Documents/python-modules/modules/mymodule.py

def yell(txt):
    print(f"{txt.upper()}")

清空 sys.path

我的疑惑是,当我清空整个 sys.path 列表时,我不应该能够导入任何模块,但令我惊讶的是我仍然可以导入内置模块。下面的代码可以正常工作。

import sys
sys.path.clear()

import math
math.ceil(10.2)

我认为可能 Python 内部并不使用 sys.pathsys.path 只是 Python 使用的原始列表的一个浅拷贝,但是当添加到 sys.path 时它是如何工作的,为什么在清空后只能导入内置模块而不能导入自定义模块呢?
我真的陷入困境了,任何帮助都将不胜感激。此外,有一个类似的问题,但它没有解答我的疑问。

enter image description here


我最好的猜测是Python在二进制文件中内置了一个位置,例如/usr/lib/python$PYTHON_VERSION,并在sys.path为空时使用它作为后备。 - TheEagle
@python_user 不,绝对不可以! - anon
@程序员 如果我从sys.path中清除某些条目而不是完全清除它,解释器是否会查看sys.path - anon
@python_user 我对模块的位置不感兴趣,我更关心解释器是如何搜索这些位置的。希望你能理解我的意思。 - anon
@Prakhar 好的!这有点讲得通,但是为什么 Python 二进制文件的位置会出现在sys.path中,当它永远不会用于内置模块时呢? - anon
显示剩余5条评论
2个回答

1

我尝试复现您的示例,但出乎意料的是没有得到相同的结果(注意:这里使用的是Python3.9)。

import sys
sys.path.clear()

import math
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'math'

然而,这个可以正常工作:

import math
del math

import sys
sys.path.clear()

import math

# but removing the reference in sys.modules will break the import again
del sys.modules['math']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'math'

我的猜测是解释器保留了先前导入的math模块的引用,因此不需要在sys.path中搜索它。

1
我在 Python 3.9.5 上尝试过,对我有效。 - python_user
有趣的是,我和一些Python聊天室的人得到了相互矛盾的结果,即使我们使用的是相同的版本。也许这是一个操作系统的问题。 - Kevin

1

CPython有一系列内置模块,例如math,其定义在文件PC/config.c中,形式如下:

struct _inittab _PyImport_Inittab[] = {

    {"_abc", PyInit__abc},
    {"array", PyInit_array},
    {"_ast", PyInit__ast},
    {"audioop", PyInit_audioop},
    {"binascii", PyInit_binascii},
    {"cmath", PyInit_cmath},
    ...
};

因此,当需要导入内置模块时,它会查找这个列表。列表中的每个“PyInit”函数都返回一个内存中的模块对象。

然后,将此列表公开为sys.builtin_module_names,该列表在sysmodule.c中初始化。接下来,在importlib._bootstrap._find_spec中的导入代码被调用,并且遍历sys.meta_path中的导入工厂列表。其中之一是importlib._bootstrap.BuiltinImporter,负责导入内置模块。这演示了sys.meta_path

>>> import sys
>>> sys.modules['math']
<module 'math' (built-in)>
>>> sys.path.clear()
>>> import math  # This works because math is in the module cache.
>>> del sys.modules['math']
>>> import math  # This works because of BuiltinImporter in sys.meta_path!
>>> sys.meta_path.clear()
>>> import math  # This still works because math is in the module cache.
>>> del sys.modules['math']
>>> import math  # This fails because we cleared sys.meta_path!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'math'

这是在Python3.7和Anaconda上运行的 - 在不同的发行版下可能会有所不同。
我想补充说明,您的测试没有考虑到sys.modules中的模块缓存。考虑以下非内置模块的示例:
>>> import requests
>>> import sys
>>> sys.path.clear()
>>> import requests  # This works!
>>> del sys.modules['requests']
>>> import requests  # This doesn't.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'requests'

好的!如果我理解你的意思正确的话,那么在清除了 sys.path 后尝试导入 requests 将会引发 ModuleNotFoundError。 - anon
是的,但只有在您先从缓存中删除它的情况下才可以。 - unddoch
这很奇怪,因为我刚刚尝试了一下,没有清除缓存,它仍然会引发异常。我所做的只是:import sys; sys.path.clear(); import requests - anon
1
请注意,在 Windows 构建中,PC/config.c 是定义该列表的位置。在其他平台上,该列表位于从 Modules/config.c.in 生成的 config.c 中。 - user2357112
@SomShekharMukherjee 我更新了我的答案,并提供了关于 sys.meta_path 的详细信息。 - unddoch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接