Python中的神坑return和finally
初识 return
返回一个值给调用者
def test(): a = 2 return a s = test() print(s)
#结果为2
如果return后面还有代码呢
def test(): a = 2 return a b = 3 return b s = test() print(s)
#结果依然为2,显然第一个return后面的代码没有执行
return 代表整个函数返回, 函数调用算结束
当 return + try..finally, 会怎样呢?
def test(): try: a = 2 return a except Exception as e: print('hahaha') finally: print('finally') s = test() print(s)
结果:
finally
2
现在借助偷窥神器dis来一探究竟,挖掘最深处的秘密.
import dis def test(): try: a = 2 return a except Exception as e: print('hahaha') finally: print('finally') print(dis.dis(test))
结果显示:
10 0 SETUP_FINALLY 56 (to 58) 2 SETUP_EXCEPT 8 (to 12) 11 4 LOAD_CONST 1 (2) 6 STORE_FAST 0 (a) 12 8 LOAD_FAST 0 (a) 10 RETURN_VALUE 13 >> 12 DUP_TOP 14 LOAD_GLOBAL 0 (Exception) 16 COMPARE_OP 10 (exception match) 18 POP_JUMP_IF_FALSE 52 20 POP_TOP 22 STORE_FAST 1 (e) 24 POP_TOP 26 SETUP_FINALLY 14 (to 42) 14 28 LOAD_GLOBAL 1 (print) 30 LOAD_CONST 2 ('hahaha') 32 CALL_FUNCTION 1 34 POP_TOP 36 POP_BLOCK 38 POP_EXCEPT 40 LOAD_CONST 0 (None) >> 42 LOAD_CONST 0 (None) 44 STORE_FAST 1 (e) 46 DELETE_FAST 1 (e) 48 END_FINALLY 50 JUMP_FORWARD 2 (to 54) >> 52 END_FINALLY >> 54 POP_BLOCK 56 LOAD_CONST 0 (None) 16 >> 58 LOAD_GLOBAL 1 (print) 60 LOAD_CONST 3 ('finally') 62 CALL_FUNCTION 1 64 POP_TOP 66 END_FINALLY 68 LOAD_CONST 0 (None) 70 RETURN_VALUE None
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
的值去执行对应的操作! 所以可以说并不是真的实质性的返回. 希望我们往后再用到它们的时候, 别再掉坑里!
dis模块研究一下!
原文:https://segmentfault.com/a/1190000010701665