Python 3上下文起止行号获取问题:Python2.7方法在3.7失效
解决Python 3.7中无法正确获取上下文起止行号的问题
你在Python 2.7里写的那段上下文管理器代码确实很巧妙,靠sys._getframe(2)拿到外层调用帧的行号,能精准捕获with块的起始和结束行。但到了Python 3.7就掉链子了,结束行号只拿到了14而不是预期的15——这其实是Python 3对帧对象的行号更新逻辑做了调整导致的。
问题根源
在Python 2.7中,当yield执行完毕回到上下文管理器时,帧对象的f_lineno会指向with块的最后一行代码。但Python 3.7及以后版本里,这个行号会停在with块中最后一条执行语句的起始行,对于跨多行的表达式(比如你写的(200, 300)),它只会记录表达式开始的行号,而不是结束行。
修复方案
要准确获取with块的起止行,我们可以换个思路:通过**抽象语法树(AST)**解析代码,找到with语句的完整范围。下面是调整后的代码:
from contextlib import contextmanager import sys import inspect import ast @contextmanager def print_start_end_ctx(): # 获取调用者的帧,拿到with语句的起始行号 caller_frame = sys._getframe(2) start_line = caller_frame.f_lineno # 获取调用者的源码并解析成AST source = inspect.getsource(caller_frame) tree = ast.parse(source) # 遍历语法树,找到对应with语句的结束行号 end_line = start_line for node in ast.walk(tree): if isinstance(node, ast.With) and node.lineno == start_line: end_line = node.end_lineno break yield print(f"start_line={start_line}\nend_line={end_line}") # 测试代码 with print_start_end_ctx(): 100 (200, 300)
代码说明
- 进入上下文时,还是通过
sys._getframe(2)拿到with语句的起始行号。 - 用
inspect.getsource获取调用者的源码,再通过ast.parse解析成语法树,AST会完整保留代码的结构信息。 - 遍历语法树找到对应行号的
With节点,它的end_lineno属性就是with块的结束行号。 - 退出上下文时,直接用之前找到的
end_line输出即可。
这段代码在Python 3.7+环境下运行,会正确输出:
start_line=22
end_line=25
(注:实际行号会根据你代码的位置变化,核心是能精准捕获with块的最后一行)
内容的提问来源于stack exchange,提问作者Saim Raza
相关产品推荐
相关产品推荐

