Bootstrap

漫谈 module caching——PyCharm jupyter notebook 在导入模块被更新后无法及时同步问题

引子:问题的发现

近日笔者用 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.
此处仍然是老的 echo 函数这便是所谓的,‘module caching’(模块缓存),即当 kernel 尚未重启时,重新运行所有的 cell 并不会从文件目录(硬盘)中再调用一遍改动后的模块函数。相反,系统会优先使用内存中已有的一份缓存副本。这种机制的设计极大地提高了模块调用的效率,因为避免了频繁的文件 I/O 操作。然而,也正是这个机制导致了上文中提到的模块未完全更新的问题,在此需要多多注意。经过笔者的反复搜寻,发现有这么两种解决方案:

  1. 每次运行所有 cell 之前先重启 kernel
    其实如果读者熟悉浏览器形式的 jupyter notebook, 会发现工具栏的 >> 按钮和 PyCharm 含义有且仅有一点差别——jupyter notebook 上是 ‘restart the kernel and run all cells’, 而 PyCharm 上则只是 ‘run all cells’。虽然 PyCharm 自身也有 ‘restart kernel’ 的按钮,但如果不想按两次,可以点击右上角的浏览器图标,在浏览器中打开 .ipynb 文件。当然,由于笔者对 PyCharm 的 UI 并不是那么熟悉,如果有读者知道更改按钮设定 / 一次性快捷完成这两项操作的方法,欢迎在评论区留言分享。
  2. 使用 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 文件,从而优化后续的模块调用效率。

参考链接


欢迎关注我的博客!
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

欢迎大家关注我,在项目上与我协作哦!

;