Python 解释器报告异常时给出意外行

Unexpected line given when Python interpreter reports exception

当 Python 解释器报告一个 error/exception(我只想说 "error" 从现在开始指代这两个),它打印行号和导致错误的行的内容。

有趣的是,如果您有一个长 运行ning Python 脚本导致错误并在脚本 运行ning 期间更改 .py 文件,那么解释器可以根据 .py 文件的更改内容报告不正确的行作为引发错误。

MWE:

sample.py

from time import sleep

for i in range(10):
    print(i)
    sleep(1)

raise Exception("foo", "bar")

此脚本 运行s 持续 10 秒,然后引发异常。

sample2.py

from time import sleep

for i in range(10):
    print(i)
    sleep(1)
"""
This
is
just
some
filler
to
demonstrate
the
behavior
"""
raise Exception("foo", "bar")

此文件与 sample.py 相同,只是它在循环结束之间有一些垃圾并且该行引发以下异常:

Traceback (most recent call last):
  File "sample.py", line 7, in <module>
Exception: ('foo', 'bar')

我做了什么

  1. python3 sample.py
  2. 在第二个终端 window,mv sample.py sample.py.bak && cp sample2.py sample.pysample.py 完成执行之前

预期行为

口译员报告如下:

Traceback (most recent call last):
  File "sample.py", line 7, in <module>
Exception: ('foo', 'bar')

在这里,解释器报告说 sample.py 的第 7 行出现异常并打印异常。

实际行为

口译员报告如下:

Traceback (most recent call last):
  File "sample.py", line 7, in <module>
    """
Exception: ('foo', 'bar')

这里解释器在报异常的时候也报"""。 它似乎是在磁盘上的文件中查找此信息,而不是将文件加载到内存中以 运行 程序。

我困惑的根源

以下是我对 运行 python3 sample.py:

时发生的情况的心智模型
  1. 解释器将sample.py的内容载入内存
  2. 解释器进行词法分析、语义分析、代码生成等产生机器码
  3. 生成的代码被发送到CPU并执行
  4. 如果出现错误,解释器会参考源代码的 内存中 表示以生成错误消息

很明显,我的心智模型存在缺陷。

我想知道的:

  1. 为什么 Python 解释器会参考磁盘上的文件来生成错误消息,而不是在内存中查找?
  2. 我对解释器正在做什么的理解是否还有其他缺陷?

根据@b_c链接的

Python doesn't keep track of what source code corresponds to any compiled bytecode. It might not even read that source code until it needs to print a traceback.

[...]

When Python needs to print a traceback, that's when it tries to find source code corresponding to all the stack frames involved. The file name and line number you see in the stack trace are all Python has to go on

[...]

The default sys.excepthook goes through the native call PyErr_Display, which eventually winds up using _Py_DisplaySourceLine to display individual source lines. _Py_DisplaySourceLine unconditionally tries to find the file in the current working directory (for some reason - misguided optimization?), then calls _Py_FindSourceFile to search sys.path for a file matching that name if the working directory didn't have it.