初识 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
会不会打印? 相信很多童鞋都想了一会, 然后说不会~ 然而这个答案是错的, 真正的输出是:
finally2
有木有觉得仿佛看见了新大陆, 在一开始的例子中, return
后面的语句没有被执行, 但是在这里, 相隔那么远, 却依旧没有忘记, 这或许就是"真爱"吧!
然而就是因为这种"真爱", 总是会让一堆新老司机掉坑里..然后还不知道为毛..
为了避免它们再继续借用打着"真爱"的幌子, 欺负我们, 让我们一起来揭开这"真爱"的真面目!
于是, 我们得借助偷窥神器: dis
, 想想都有点小兴奋!
import disdef 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.cTARGET(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.cvoid 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.cTARGET_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
的值去执行对应的操作! 所以可以说并不是真的实质性的返回. 希望我们往后再用到它们的时候, 别再掉坑里!
作者:51reboot
链接:https://www.jianshu.com/p/333312f8190d
共同学习,写下你的评论
评论加载中...
作者其他优质文章