浅析Python中return和finally共同挖的坑
|
前言 本文主要给大家介绍了在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. 第一列是代码在文件的行号 在字节码中可以看到,依次是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设置:
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,&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的值去执行对应的操作! 所以可以说并不是真的实质性的返回. 希望我们往后再用到它们的时候,别再掉坑里! 好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对编程小技巧的支持。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |








