引子:问题的发现
近日笔者用 PyCharm 创建了一个项目时不经意间发现了这个问题:事情发生在调试 Jupyter Notebook 的过程中。当笔者修改了自己编写的某个库函数后,重新运行代码时却发现,Notebook 仍然调用的是修改前的旧版本库函数。经过查阅大量资料和反复测试,终于弄清楚,问题的根源在于 Python 自带的模块缓存机制。笔者也发现,国内资源中对于这一问题也没有过多详细的解释,因此决定写下此文,以供遇到相同问题的读者们参考,也欢迎大家提出建议。
何为 module caching
(这一部分可以在 PyCharm 中动手实验,本人所用版本为 2024.3 专业版。)
我们新建一个项目,其中创建两个文件:
```a.py```
def echo():
print(1)
```b.ipynb```
from EM.a import echo
echo()
运行 b.ipynb
, 如我们所料,程序正常输出了 1
. 但假如我们改动 a.py
中函数的输出,将打印的 1
改为 2
并保存,照理来说重新运行 b.ipynb
应该会重新导入一遍这个新的函数吧?但实际运行发现,调用的函数仍然是老版本的,其输出为 1
.
这便是所谓的,‘module caching’(模块缓存),即当 kernel 尚未重启时,重新运行所有的 cell 并不会从文件目录(硬盘)中再调用一遍改动后的模块函数。相反,系统会优先使用内存中已有的一份缓存副本。这种机制的设计极大地提高了模块调用的效率,因为避免了频繁的文件 I/O 操作。然而,也正是这个机制导致了上文中提到的模块未完全更新的问题,在此需要多多注意。经过笔者的反复搜寻,发现有这么两种解决方案:
- 每次运行所有 cell 之前先重启 kernel
其实如果读者熟悉浏览器形式的 jupyter notebook, 会发现工具栏的>>
按钮和 PyCharm 含义有且仅有一点差别——jupyter notebook 上是 ‘restart the kernel and run all cells’, 而 PyCharm 上则只是 ‘run all cells’。虽然 PyCharm 自身也有 ‘restart kernel’ 的按钮,但如果不想按两次,可以点击右上角的浏览器图标,在浏览器中打开.ipynb
文件。当然,由于笔者对 PyCharm 的 UI 并不是那么熟悉,如果有读者知道更改按钮设定 / 一次性快捷完成这两项操作的方法,欢迎在评论区留言分享。 - 使用
importlib
中的reload
函数
当然,万能的 Python 总是能给我们提供奇妙的库函数来解决问题。这里用到的函数是importlib
库中的reload
函数,能够强制在同一进程下从硬盘中重新获取更新后的库文件,并在下一次调用时使用更新后的函数。具体操作方法如下:```b.ipynb``` from importlib import reload import EM.a reload(EM.a) # 重新加载模块 from EM.a import echo # 加载后导入需要的函数 echo()
见微知著:Python 中的缓存机制
事实上,上述所说的缓存机制只是 python 中的一种。而 python 的缓存按范围可以分为两类:单进程中的缓存机制和进程之间的缓存机制。虽然我们平时只需一下 Ctrl+F5
就能保存并重新运行一个 .py
文件,但前后两次运行是两个不同的进程。而到了 .ipynb
的 notebook 文件中,重新运行 cell 而不重启 kernel 实际上还是留在原来的老进程中(这点从我们在 cmd
输入 jupyter notebook
后只要不重启 kernel 命令行就不会断这一事实也不难看出)。这一分类下,其实我们刚才提到的缓存属于第一类。
- 单进程:具体来说,每当你在同一进程中使用
import
语句导入某个模块时,Python 不会重复加载该模块的内容,而是直接从全局字典(sys.modules
)中获取对应的模块对象。这种设计不仅提升了运行效率,避免了重复的磁盘 I/O 操作,还确保了模块在进程中的状态得以持久化。 - 进程间:多个解释器进程加载相同的 Python 模块时,在第一次加载时,Python 会将
.py
源代码编译为包含字节码的.pyc
文件。这一操作只需进行一次,因此后续的进程无需重新解析.py
文件,加载速度会快上不少。这种机制在安装新库时尤为显著。例如,当我们使用工具安装一个 Python 库时,系统会自动生成.pyc
文件,从而优化后续的模块调用效率。
参考链接
- https://stackoverflow.com/questions/2918898/prevent-python-from-caching-the-imported-modules
- https://stackoverflow.com/questions/29353600/ipython-notebook-caching-issue
- https://www.reddit.com/r/Python/comments/140c9z9/does_python_cache_package_imports/
欢迎关注我的博客!
Find me on GitHub: GitHub profile page
Gitee account (under construction): Gitee site
GitLab account (under construction): GitLab site
Also find me on Luogu:Luogu profile
欢迎大家关注我,在项目上与我协作哦!