浅析Python中return和finally共同挖的坑
发布时间 - 2026-01-11 02:51:20 点击率:次前言

本文主要给大家介绍了在Python中return和finally共同存在的坑,以及填坑经验,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。
初识 return
相信每一个用过Python函数的童鞋, 肯定会用过return语句, return顾名思义, 就是用来返回值给调用者, 例如:
def test(): a = 2 return a s = test() print s # 输出结果 2
对于上面的结果, 相信大家都不会感到意外, 那么加大点难度, 如果在return语句还有代码呢? 那句代码会怎样呢?
def test(): a = 2 return a s = 3 print s s = test() print s # 结果是什么?
老司机肯定一眼就能看出结果, 但是对于尚在入门或者对return不很了解的童鞋, 可能就会懵逼了~ 后面的两句代码是否会被执行?
答案是: 不会执行
return正如它的名字那样, 当执行这句代码, 整个函数都会返回, 整个调用就算结束了~ 所以在return后面的代码, 都是不会被执行的!
也正因为这个特性, 所以有种编码规范叫early return的编码规范就被倡导
它的意思大概就是: 当条件已经满足返回时, 就马上返回
举个例子来说明:
def test(): a = 2 if a > 2: result = 'more than' else: result = 'less than' return result s = test() print s
上面的代码应该比较容易理解, 就是根据a的值, 来决定返回的result是什么. 这样的编码相信也是大部分童鞋喜欢用的, 因为这样比较符合我们直觉, 然而, 这样写似乎有点浪费, 因为当第一个判断结束了, 如果结果为真, 就应该返回more than, 然后结束函数, 否则肯定就是返回less than, 所以我们可以把代码调整成这样:
def test(): a = 2 if a > 2: return 'more than' else: return 'less than' s = test() print s
甚至是:
def test(): a = 2 if a > 2: return 'more than' return 'less than' s = test() print s
结果都是和第一个写法是一样的! 第一次看到这样写法的童鞋, 可能会觉得比较难以接受, 甚至觉得可读性很差, 但是其实这样的写法, 我觉得反而会稍微好点. 因为:
- 运行的代码数少了, 调用方能更快得到结果
- 有利于减少嵌套的层数, 便于理解.
对于第2点在这需要解释下, 很多时候我们写得代码, 嵌套很深, 都是因为if/else的锅, 因为嵌套的if/else 比较多, 所以导致一堆代码都嵌套得比较深, 这样对于其他小伙伴, 简直就是灾难, 因为他们很可能在阅读这部分代码时, 就忘了前面的逻辑....
为了更加容易理解, 举个代码例子:
def test():
a = 2
if a > 2:
result = 'not 2'
else:
a += 2
if a < 2:
result = 'not 2'
else:
for i in range(2):
print 'test ~'
result = 'Target !'
return result
s = test()
print s
# 输出结果
test ~
test ~
Target !
代码简化优化版:
def test(): a = 2 if a > 2: return 'not 2' a += 2 if a < 2: return 'not 2' for i in range(2): print 'test ~' return 'Target !' s = test() print s # 输出结果 test ~ test ~ Target !
这样对比这来看, 应该能更好地理解为什么说early return能够减少嵌套的层数吧~ 有疑问欢迎留言讨论~
谈谈深坑
刚才花了比较长的篇幅去介绍return, 相信看到这里, 对于return应该有比较基本的理解了! 所以来聊聊更加迷惑的话题:
当 return 遇上 try..finally, 会怎样呢?
如果刚才有认真看的话, 会注意到一句话, 就是:
return 代表整个函数返回, 函数调用算结束
但事实真的这样吗? 通常这样问, 答案一般都不是 ~~
先来看看例子:
def test(): try: a = 2 return a except: pass finally: print 'finally' s = test() print s
可以猜猜这句print a会不会打印? 相信很多童鞋都想了一会, 然后说不会~ 然而这个答案是错的, 真正的输出是:
finally 2
有木有觉得仿佛看见了新大陆, 在一开始的例子中, return后面的语句没有被执行, 但是在这里, 相隔那么远, 却依旧没有忘记, 这或许就是"真爱"吧!
然而就是因为这种"真爱", 总是会让一堆新老司机掉坑里..然后还不知道为毛..
为了避免它们再继续借用打着"真爱"的幌子, 欺负我们, 让我们一起来揭开这"真爱"的真面目!
于是, 我们得借助偷窥神器: dis, 想想都有点小兴奋!
import dis def test(): try: a = 2 return a except: pass finally: print 'finally' print dis.dis(test)
输出比较长, 单独写:
# 输出结果
6 0 SETUP_FINALLY 28 (to 31)
3 SETUP_EXCEPT 14 (to 20)
7 6 LOAD_CONST 1 (2)
9 STORE_FAST 0 (a)
8 12 LOAD_FAST 0 (a)
15 RETURN_VALUE
16 POP_BLOCK
17 JUMP_FORWARD 7 (to 27)
9 >> 20 POP_TOP
21 POP_TOP
22 POP_TOP
10 23 JUMP_FORWARD 1 (to 27)
26 END_FINALLY
>> 27 POP_BLOCK
28 LOAD_CONST 0 (None)
13 >> 31 LOAD_CONST 2 ('finally')
34 PRINT_ITEM
35 PRINT_NEWLINE
36 END_FINALLY
37 LOAD_CONST 0 (None)
40 RETURN_VALUE
这边简单说着这些列所代表的意思:
1. 第一列是代码在文件的行号
2. 第二列字节码的偏移量
3. 字节码的名字
4. 参数
5. 字节码处理参数最终的结果
在字节码中可以看到, 依次是SETUP_FINALLY 和 SETUP_EXCEPT, 这个对应的就是finally和try,虽然finally在try后面, 虽然我们通常帮他们看成一个整体, 但是他们在实际上却是分开的... 因为我们重点是finally, 所以就单单看SETUP_FINALLY
// ceval.c
TARGET(SETUP_FINALLY)
_setup_finally:
{
/* NOTE: If you add any new block-setup opcodes that
are not try/except/finally handlers, you may need
to update the PyGen_NeedsFinalizing() function.
*/
PyFrame_BlockSetup(f, opcode, INSTR_OFFSET() + oparg,
STACK_LEVEL());
DISPATCH();
}
// fameobject.c
void
PyFrame_BlockSetup(PyFrameObject *f, int type, int handler, int level)
{
PyTryBlock *b;
if (f->f_iblock >= CO_MAXBLOCKS)
Py_FatalError("XXX block stack overflow");
b = &f->f_blockstack[f->f_iblock++];
b->b_type = type;
b->b_level = level;
b->b_handler = handler;
}
从上面的代码, 很明显就能看出来, SETUP_FINALLY 就是调用下PyFrame_BlockSetup去创建一个Block, 然后为这个Block设置:
- b_type (opcode 也就是SETUP_FINALLY)
- b_level
- b_handler (INSTR_OFFSET() + oparg)
handler 可能比较难理解, 其实看刚才的 dis 输出就能看到是哪个, 就是 13 >> 31 LOAD_CONST 2 ('finally'), 这个箭头就是告诉我们跳转的位置的, 为什么会跳转到这句呢? 因为6 0 SETUP_FINALLY 28 (to 31)已经告诉我们将要跳转到31这个位置~~~
如果这个搞清楚了, 那就再来继续看 return, return对应的字节码是: RETURN_VALUE, 所以它对应的源码是:
// ceval.c
TARGET_NOARG(RETURN_VALUE)
{
retval = POP();
why = WHY_RETURN;
goto fast_block_end;
}
原来我们以前理解的return是假return! 这个return并没有直接返回嘛, 而是将堆栈的值弹出来, 赋值个retval, 然后将why设置成WHY_RETURN, 接着就跑路了! 跑到一个叫fast_block_end;的地方~, 没办法, 为了揭穿真面目, 只好掘地三尺了:
while (why != WHY_NOT && f->f_iblock > 0) {
fast_block_end:
while (why != WHY_NOT && f->f_iblock > 0) {
/* Peek at the current block. */
PyTryBlock *b = &f->f_blockstack[f->f_iblock - 1];
assert(why != WHY_YIELD);
if (b->b_type == SETUP_LOOP && why == WHY_CONTINUE) {
why = WHY_NOT;
JUMPTO(PyInt_AS_LONG(retval));
Py_DECREF(retval);
break;
}
/* Now we have to pop the block. */
f->f_iblock--;
while (STACK_LEVEL() > b->b_level) {
v = POP();
Py_XDECREF(v);
}
if (b->b_type == SETUP_LOOP && why == WHY_BREAK) {
why = WHY_NOT;
JUMPTO(b->b_handler);
break;
}
if (b->b_type == SETUP_FINALLY ||
(b->b_type == SETUP_EXCEPT &&
why == WHY_EXCEPTION) ||
b->b_type == SETUP_WITH) {
if (why == WHY_EXCEPTION) {
PyObject *exc, *val, *tb;
PyErr_Fetch(&exc, &val, &tb);
if (val == NULL) {
val = Py_None;
Py_INCREF(val);
}
/* Make the raw exception data
available to the handler,
so a program can emulate the
Python main loop. Don't do
this for 'finally'. */
if (b->b_type == SETUP_EXCEPT ||
b->b_type == SETUP_WITH) {
PyErr_NormalizeException(
&exc, &val, &tb);
set_exc_info(tstate,
exc, val, tb);
}
if (tb == NULL) {
Py_INCREF(Py_None);
PUSH(Py_None);
} else
PUSH(tb);
PUSH(val);
PUSH(exc);
}
else {
if (why & (WHY_RETURN | WHY_CONTINUE))
PUSH(retval);
v = PyInt_FromLong((long)why);
PUSH(v);
}
why = WHY_NOT;
JUMPTO(b->b_handler);
break;
}
} /* unwind stack */
在这需要回顾下刚才的一些知识, 刚才我们看了return的代码, 看到它将why设置成了 WHY_RETURN, 所以在这么一大串判断中, 它只是走了最后面的else, 动作也很简单, 就是将刚才return储存的值retval再push压回栈, 同时将why转换成long再压回栈, 然后有设置了下why,接着就是屁颠屁颠去执行刚才SETUP_FINALLY设置的b_handler代码了~ 当这这段bhandler代码执行完, 就再通过END_FINALLY去做回该做的事, 而这里就是, return retval
结论
所以, 我们应该能知道为什么当我们执行了return代码, 为什么finally的代码还会先执行了吧, 因为return的本质, 就是设置why和retval, 然后goto到一个大判断, 最后根据why的值去执行对应的操作! 所以可以说并不是真的实质性的返回. 希望我们往后再用到它们的时候, 别再掉坑里!
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。
# return
# finally
# python
# 用法
# 中的finally
# python 实现return返回多个值
# Python中print和return的作用及区别解析
# Python中return self的用法详解
# 对python中return和print的一些理解
# python函数中return后的语句一定不会执行吗?
# Python中return语句用法实例分析
# Python中exit、return、sys.exit()等使用实例和区别
# python return逻辑判断表达式实现解析
# 都是
# 童鞋
# 这句
# 第一个
# 就能
# 在这
# 告诉我们
# 用过
# 真面目
# 行号
# 深坑
# 较长
# 跳转到
# 层数
# 就会
# 结束了
# 在这里
# 成了
# 好了
# 看了
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel怎么在Blade中安全地输出原始HTML内容
Gemini手机端怎么发图片_Gemini手机端发图方法【步骤】
北京网站制作的公司有哪些,北京白云观官方网站?
胶州企业网站制作公司,青岛石头网络科技有限公司怎么样?
网站制作大概要多少钱一个,做一个平台网站大概多少钱?
Laravel中间件起什么作用_Laravel Middleware请求生命周期与自定义详解
如何用AWS免费套餐快速搭建高效网站?
java中使用zxing批量生成二维码立牌
Swift中swift中的switch 语句
JavaScript Ajax实现异步通信
深圳网站制作培训,深圳哪些招聘网站比较好?
如何在Tomcat中配置并部署网站项目?
北京的网站制作公司有哪些,哪个视频网站最好?
javascript如何操作浏览器历史记录_怎样实现无刷新导航
HTML 中动态设置元素 name 属性的正确语法详解
如何快速查询网址的建站时间与历史轨迹?
Laravel如何将应用部署到生产服务器_Laravel生产环境部署流程
Laravel Livewire是什么_使用Laravel Livewire构建动态前端界面
香港服务器网站生成指南:免费资源整合与高速稳定配置方案
北京企业网站设计制作公司,北京铁路集团官方网站?
哪家制作企业网站好,开办像阿里巴巴那样的网络公司和网站要怎么做?
制作无缝贴图网站有哪些,3dmax无缝贴图怎么调?
香港服务器建站指南:外贸独立站搭建与跨境电商配置流程
浅析上传头像示例及其注意事项
Laravel怎么实现软删除SoftDeletes_Laravel模型回收站功能与数据恢复【步骤】
Laravel怎么创建自己的包(Package)_Laravel扩展包开发入门到发布
Laravel如何为API生成Swagger或OpenAPI文档
如何快速上传自定义模板至建站之星?
企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?
html文件怎么打开证书错误_https协议的html打开提示不安全【指南】
Linux安全能力提升路径_长期防护思维说明【指导】
Laravel如何使用Eloquent进行子查询
Laravel Blade组件怎么用_Laravel可复用视图组件的创建与使用
网站制作价目表怎么做,珍爱网婚介费用多少?
网站页面设计需要考虑到这些问题
如何使用 jQuery 正确渲染 Instagram 风格的标签列表
html5如何实现懒加载图片_ intersectionobserver api用法【教程】
宙斯浏览器文件分类查看教程 快速筛选视频文档与图片方法
如何在万网开始建站?分步指南解析
如何续费美橙建站之星域名及服务?
Edge浏览器提示“由你的组织管理”怎么解决_去除浏览器托管提示【修复】
Windows10怎样连接蓝牙设备_Windows10蓝牙连接步骤【教程】
ChatGPT怎么生成Excel公式_ChatGPT公式生成方法【指南】
JavaScript如何实现类型判断_typeof和instanceof有什么区别
Laravel如何实现模型的全局作用域?(Global Scope示例)
Laravel Seeder怎么填充数据_Laravel数据库填充器的使用方法与技巧
浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】
如何在局域网内绑定自建网站域名?
javascript基于原型链的继承及call和apply函数用法分析
Win11怎么更改系统语言为中文_Windows11安装语言包并设为显示语言

