PAYATHON 2026

如何预初始化 Python 解释器并保留脚本 traceback 行号?

数据小许

结论

不要逐行把用户脚本写入非交互模式 Python 进程的 stdin。可以让子进程运行固定的 worker 程序:先初始化 C# 环境,再从 stdin 读取脚本路径,最后通过 runpy.run_path() 执行脚本文件。这样 Python 会按真实文件编译脚本,异常 traceback 也会包含对应的文件名和行号。

例如,worker 可以这样写:

# worker.py
import json
import runpy
import sys
import traceback


def init_csharp():
    # 在这里加载并初始化 C# / pythonnet 基础设施
    pass


init_csharp()

# 初始化完成后再读取任务。父进程也可以在初始化期间先写入路径,
# 这条输入会留在管道中,等 worker 读到时初始化已经完成。
request = json.loads(sys.stdin.readline())
script_path = request["path"]

try:
    runpy.run_path(script_path, run_name="__main__")
except BaseException:
    traceback.print_exc()

父进程传递脚本路径,而不是脚本源码:

import json
import subprocess
import sys

proc = subprocess.Popen(
    [sys.executable, "-u", "worker.py"],
    stdin=subprocess.PIPE,
    text=True,
)

proc.stdin.write(json.dumps({"path": "/path/to/user_script.py"}) + "\n")
proc.stdin.flush()
proc.stdin.close()
proc.wait()

-u 会让 Python 的标准流使用无缓冲模式。这个示例没有捕获 stdout 和 stderr,因此即使用户脚本输出较多,也不会因为父进程没有及时读取管道而卡住。若要捕获输出,需要持续读取这些管道,或将输出重定向到文件。

为什么逐行写入 stdin 会延迟执行

Python 通常会把从管道读取的标准输入当作脚本内容处理,而不会像终端交互会话那样,每收到一行就执行一行。父进程持续写入时,解释器可能会继续等待;stdin 关闭后,解释器才知道输入结束,并执行收到的源码。-i 会影响脚本结束后的交互行为,但无法让管道成为可靠的逐行任务协议。

逐行发送多行脚本还会丢失原始文件上下文。Python 收到的是 stdin 中的一段输入,不是按用户脚本路径编译的文件,因此 traceback 无法可靠地显示原脚本的文件名和行号。

保留 traceback 信息

runpy.run_path(script_path, run_name="__main__") 会按脚本路径执行文件,并将运行名称设为 __main__。如果异常未被捕获,Python 会正常输出 traceback;上面的代码捕获异常后调用 traceback.print_exc(),显示的仍是原脚本中的异常位置。

如果脚本依赖命令行参数,执行前需要设置 sys.argv,例如:

sys.argv = [script_path, "--option", "value"]
runpy.run_path(script_path, run_name="__main__")

如果必须通过管道传递源码,而不能传递文件路径,也可以显式指定文件名来编译:

source = sys.stdin.read()
code = compile(source, "/path/to/user_script.py", "exec")
exec(code, {"__name__": "__main__", "__file__": "/path/to/user_script.py"})

指定的文件名会出现在 traceback 中,但源码仍要完整读入后才能编译执行。对于已经存在的脚本文件,直接使用 runpy.run_path() 更简单。

复用解释器时的注意事项

如果 worker 会连续执行多个脚本,需要明确任务边界和错误处理方式。Python 进程中的模块缓存、全局状态和 UI 事件循环也可能在任务间保留。如果每个任务都需要相互隔离,执行完一个脚本后退出进程,再从池中取一个已初始化的新进程会更稳妥。使用 PyQt 或 Tk 时,还要确保 UI 操作符合对应框架的线程和事件循环要求。

备注:内容仅供参考。