VSCode Jupyter print 函数输出在错误的单元格(cell)

问题描述

最近在复现一个开源项目时,发现执行过该项目中的代码单元格后,其余单元格的print函数输出也会续在该单元格后。而正常情况下print函数输出应该位于其所属的单元格。下图中,我将出现问题的单元格执行后清空了输出,但是在其他单元格执行 print 函数后,输出还是会打印到该单元格后。而在执行该单元格前 print 功能表现正常。重启电脑无效。

VSCode Jupyter print 函数输出在错误的单元格(cell)_第1张图片

原因

一开始是以为是bug,但是重启电脑都没有看到问题解决,推测多半是正常的原因。而出现问题的单元格是引用开源代码,内部执行逻辑较为复杂,从函数内部找原因耗时较久。

于是突然想到是不是 print 的输出流定位错误了,于是搜了一下print的默认输出流。

In Python, the default output stream for the print() function is sys.stdout.

于是就打印一下 stdout。

import sys
print(sys.stdout)

输出结果如下:

`<_io.TextIOWrapper name='' mode='w' encoding='utf-8'> `

看上去好像没啥问题?同样的代码新建了一个文件测试,得到的输出如下:

``

果然是有问题的。

我尝试了直接获取原来的默认的 ipykernel 的 OutStream,翻了官方的源码[1],没有找到直接获取 OutStream 的方法。花了一小段时间折腾出了下面的代码,自己新建一个 stream 。能用但是用warning,不知道有没有隐藏风险,再次记录一下:

import sys
from IPython.core.getipython import get_ipython
from ipykernel.iostream import OutStream
kernel = get_ipython().kernel
default_out = OutStream(kernel.session, kernel.iopub_socket, name='stdout')
sys.stdout = default_out

会提示 warning:

/tmp/ipykernel_3746745/352382136.py:2: DeprecationWarning: Since IPykernel 4.3, OutStream should be created with IOPubThread, not default_out = OutStream(kernel.session, kernel.iopub_socket, name='stdout')

测试了一下不太行。所以就只有朴素的两种解决方案列在下面了。

解决方案

1. 避免 sys.stdout 的操作

检查出现问题的单元格所调用的代码,你应该最终能找到对 sys.stdout 做操作的代码。以我的情况为例,问题的原因是下面的代码。

VSCode Jupyter print 函数输出在错误的单元格(cell)_第2张图片

大概是因为代码的原作者中间调用了一部分输出很多的代码,懒得处理就直接把stdout指向虚空,跑完了再改回来,但是 sys.__stdout__ 并不是 Jupyter 默认的 out stream,所以就回不来了。

第一种思路就是把这里做一些处理(注释或删除),取消掉对 sys.stdout 的操作。

2. 处理 sys.stdout 前备份,操作后还原

另一种思路则是备份默认的 Jupyter OutStream,在执行代码块前将 sys.stdout 保存到一个临时变量中,然后执行结束后再将临时变量的值赋回给 stdout。

original_stdout = sys.stdout
# 原有的造成问题的代码
# xxx
sys.stdout = original_stdout

备注

这个问题非常少见,谷歌也没有找到类似的情况,遇到这种情况也是非常难了。但是解决方案其实很简单,做个备份事后回复就好。

你可能感兴趣的:(jupyter,python,vscode)