为什么在 Windows 上的 Python 3 下创建模块后导入失败?

Why does importing fail after creating a module under Python 3 on Windows?

以下代码尝试创建然后导入两个模块:

# coding: utf-8

import os
import time

# Remove the modules we're about to create if they already exist
def force_unlink(name):
    try:
        os.unlink(name)
    except OSError:
        pass
force_unlink("print1.py")
force_unlink("print1.pyc")
force_unlink("print2.py")
force_unlink("print2.pyc")
time.sleep(1)

# Create module 1 and module 2, then try to import them just afterwards
print("Creating module 1...")
with open("print1.py", "wb+") as fd:
    fd.write(b'print("Imported module 1")')
import print1
print("Creating module 2...")
with open("print2.py", "wb+") as fd:
    fd.write(b'print("Imported module 2")')
import print2

在 Windows 上,两个导入都在 Python 2 (2.7) 下工作,但在 Python 3(3.5 和 3.6)下不工作:

$ python2 reproduce.py
Creating module 1...
Imported module 1
Creating module 2...
Imported module 2
$ python3 reproduce.py
Creating module 1...
Imported module 1
Creating module 2...
Traceback (most recent call last):
  File "reproduce.py", line 26, in <module>
    import print2
ImportError: No module named 'print2'

在每个 import printX 调用之前添加 time.sleep(5) 使其工作。

这是为什么?

注意:这是 issue 我想弄明白的一个更简单的版本。

我想我知道发生了什么。新的 Python 3 导入机制 缓存 它在目录中找到的文件名。当目录的修改时间mtime发生变化时,它会重新加载缓存。

查看 importlib._bootstrap_external.FileFinder.find_spec() method implementation,其中包含:

try:
    mtime = _path_stat(self.path or _os.getcwd()).st_mtime
except OSError:
    mtime = -1
if mtime != self._path_mtime:
    self._fill_cache()
    self._path_mtime = mtime

此处 _path_stat 只是一个 os.stat() 调用,但已本地化以避免导入。 _fill_cache() 方法执行 os.listdir() 调用。

在某些 Windows 文件系统上,mtime 的分辨率非常低,最多 2 秒。对于您的情况,分辨率显然仍然足够低,以至于在您尝试加载第二个模块时 不会 更新缓存。尽管 NTFS 文件系统可以以 100ns 的增量记录时间,但实际上限制因素似乎是 Windows 系统时钟,据我所知通常限于 15ms 的分辨率。因此,如果您在写入 print1.py 的 15 毫秒内写入 print2.py,那么 Python 将不会注意到。

Python 确实为您提供了清除此缓存的方法;使用 importlib.invalidate_caches() method;这会将 FileFinder 实例上的 _path_mtime 属性重置回 -1,强制进行新的 _fill_cache() 调用。

正如该函数的文档所述:

This function should be called if any modules are created/installed while your program is running to guarantee all finders will notice the new module’s existence.