Python 文档最后对 return 的解释具有误导性
Python docs have misleading explanation of return in finally
我正在阅读 python 文档来改进我的核心 python 我正在阅读 errors and exceptions
文档中说
If a finally clause includes a return statement, the finally clause’s return statement will execute before, and instead of, the return statement in a try clause.
下面还提供了这个例子:
def bool_return():
try:
return True
finally:
return False
bool_return()
现在看这个例子,上面的陈述看起来很直接和公平,但是如果你稍微修改那个例子让它看起来像这样:
def bool_return():
try:
return print("foo")
finally:
return False
bool_return()
现在,如果您 运行 这样做,您将看到 foo
将被打印,而 False 将被 return 编辑。现在文档说 finally 子句的 return 将执行 before,而 而不是 try 子句的 return 语句。如果是这样,那为什么我可以看到正在打印的 foo?
我用 pycharm 调试了这段代码,它显示首先执行 try 子句的 return 语句并打印字符串,然后它的输出 None
是 returned由于return
语句,finally子句中的return语句会在后面执行,也就是程序的最后一个return所以函数会覆盖前面的return 和 False
是 returned.
我的问题是:
1) 为什么文档说 finally 子句的 return 语句在 之前 执行?
2) 为什么 doc 说 finally 子句的 return 语句被执行 而不是 try 子句的 return 语句?
我相信这两种说法都与现实情况相反。
编辑:
阅读@iBug 的回答后,现在很清楚 print("foo")
是如何计算的,但 None
不是 return。基本上,首先计算表达式,然后 return
发生。稍后 return False
in finally 被执行。这清楚地说明了为什么我们得到了我们所做的输出。
不过,我看到 finally 中的 return False
是在after the return print("foo")
of try.
之后执行的
或者按照@iBug 的评论,10 RETURN_VALUE
被完全绕过了?
编辑
这已在文档中得到解决,现在 returned 的内容是正确的。但是,如果你想知道 "how" 请阅读所有评论并仔细回答。
$ python3
Python 3.7.5 (default, Nov 20 2019, 09:21:52)
[GCC 9.2.1 20191008] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def bool_return():
... try:
... return print("foo")
... finally:
... return False
...
>>> import dis
>>> dis.dis(bool_return)
2 0 SETUP_FINALLY 8 (to 10)
3 2 LOAD_GLOBAL 0 (print)
4 LOAD_CONST 1 ('foo')
6 CALL_FUNCTION 1
8 RETURN_VALUE
5 >> 10 LOAD_CONST 2 (False)
12 RETURN_VALUE
>>>
正如您在上面看到的,return False
是否发生在 try
块中的 return
语句之前,但在 to- be-returned 值已经计算出来。
我认为文档可能意味着 "the very action of returning" 通过 return 语句 ,或者换句话说,它没有考虑到return 值,这当然发生在 returned 之前。
观察8 RETURN_VALUE
是否执行,可以在调试模式下编译CPython解释器,在GDB中运行编译。一步一步的指南对于这个答案来说太臃肿了,所以我会在这里给出一个大纲 (Linux)。
- 从官方来源(python.org 网站或GitHub)获取 CPython 源代码
- 配置调试版本
./configure --with-pydebug
(您可能还想提供 --prefix=/opt/python3-debug
)、make
和 make install
- 在 GDB 中启动调试 Python:
gdb /opt/python3-debug/bin/python3
和 (gdb) r
- 照常定义函数
bool_return
。
- 在
Python/ceval.c
中找到字符串RETURN_VALUE
,记下行号(for 3.8.1, it's 1911)。
- 通过发送
SIGTRAP
挂起Python解释器,并在上一步(b Python/ceval.c:1911
)的位置设置断点,然后c
.
- 观察两次到达断点,输出如下:
(gdb breakpoint info)
False
(gdb breakpoint info)
- 观察您在 REPL 中输入的每个语句如何到达断点一次。这是了解到上面步骤中的第二个断点是由Python REPL引起的,所以只有第一个断点来自于函数中的一个
return
语句。
现在很明显函数里只执行了一条return
,肯定是12 RETURN_VALUE
,所以Python指令8 RETURN_VALUE
没有执行完全没有。
我正在阅读 python 文档来改进我的核心 python 我正在阅读 errors and exceptions
文档中说
If a finally clause includes a return statement, the finally clause’s return statement will execute before, and instead of, the return statement in a try clause.
下面还提供了这个例子:
def bool_return():
try:
return True
finally:
return False
bool_return()
现在看这个例子,上面的陈述看起来很直接和公平,但是如果你稍微修改那个例子让它看起来像这样:
def bool_return():
try:
return print("foo")
finally:
return False
bool_return()
现在,如果您 运行 这样做,您将看到 foo
将被打印,而 False 将被 return 编辑。现在文档说 finally 子句的 return 将执行 before,而 而不是 try 子句的 return 语句。如果是这样,那为什么我可以看到正在打印的 foo?
我用 pycharm 调试了这段代码,它显示首先执行 try 子句的 return 语句并打印字符串,然后它的输出 None
是 returned由于return
语句,finally子句中的return语句会在后面执行,也就是程序的最后一个return所以函数会覆盖前面的return 和 False
是 returned.
我的问题是:
1) 为什么文档说 finally 子句的 return 语句在 之前 执行?
2) 为什么 doc 说 finally 子句的 return 语句被执行 而不是 try 子句的 return 语句?
我相信这两种说法都与现实情况相反。
编辑:
阅读@iBug 的回答后,现在很清楚 print("foo")
是如何计算的,但 None
不是 return。基本上,首先计算表达式,然后 return
发生。稍后 return False
in finally 被执行。这清楚地说明了为什么我们得到了我们所做的输出。
不过,我看到 finally 中的 return False
是在after the return print("foo")
of try.
或者按照@iBug 的评论,10 RETURN_VALUE
被完全绕过了?
编辑
这已在文档中得到解决,现在 returned 的内容是正确的。但是,如果你想知道 "how" 请阅读所有评论并仔细回答。
$ python3
Python 3.7.5 (default, Nov 20 2019, 09:21:52)
[GCC 9.2.1 20191008] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def bool_return():
... try:
... return print("foo")
... finally:
... return False
...
>>> import dis
>>> dis.dis(bool_return)
2 0 SETUP_FINALLY 8 (to 10)
3 2 LOAD_GLOBAL 0 (print)
4 LOAD_CONST 1 ('foo')
6 CALL_FUNCTION 1
8 RETURN_VALUE
5 >> 10 LOAD_CONST 2 (False)
12 RETURN_VALUE
>>>
正如您在上面看到的,return False
是否发生在 try
块中的 return
语句之前,但在 to- be-returned 值已经计算出来。
我认为文档可能意味着 "the very action of returning" 通过 return 语句 ,或者换句话说,它没有考虑到return 值,这当然发生在 returned 之前。
观察8 RETURN_VALUE
是否执行,可以在调试模式下编译CPython解释器,在GDB中运行编译。一步一步的指南对于这个答案来说太臃肿了,所以我会在这里给出一个大纲 (Linux)。
- 从官方来源(python.org 网站或GitHub)获取 CPython 源代码
- 配置调试版本
./configure --with-pydebug
(您可能还想提供--prefix=/opt/python3-debug
)、make
和make install
- 在 GDB 中启动调试 Python:
gdb /opt/python3-debug/bin/python3
和 (gdb)r
- 照常定义函数
bool_return
。 - 在
Python/ceval.c
中找到字符串RETURN_VALUE
,记下行号(for 3.8.1, it's 1911)。 - 通过发送
SIGTRAP
挂起Python解释器,并在上一步(b Python/ceval.c:1911
)的位置设置断点,然后c
. - 观察两次到达断点,输出如下:
(gdb breakpoint info)
False
(gdb breakpoint info)
- 观察您在 REPL 中输入的每个语句如何到达断点一次。这是了解到上面步骤中的第二个断点是由Python REPL引起的,所以只有第一个断点来自于函数中的一个
return
语句。
现在很明显函数里只执行了一条return
,肯定是12 RETURN_VALUE
,所以Python指令8 RETURN_VALUE
没有执行完全没有。