摘要:初識(shí)相信每一個(gè)用過函數(shù)的童鞋肯定會(huì)用過語句顧名思義就是用來返回值給調(diào)用者例如輸出結(jié)果對(duì)于上面的結(jié)果相信大家都不會(huì)感到意外那么加大點(diǎn)難度如果在語句還有代碼呢那句代碼會(huì)怎樣呢結(jié)果是什么老司機(jī)肯定一眼就能看出結(jié)果但是對(duì)于尚在入門或者對(duì)不很了解的童
初識(shí) return
??相信每一個(gè)用過Python函數(shù)的童鞋, 肯定會(huì)用過return語句, return顧名思義, 就是用來返回值給調(diào)用者, 例如:
def test(): a = 2 return a s = test() print s # 輸出結(jié)果 2
對(duì)于上面的結(jié)果, 相信大家都不會(huì)感到意外, 那么加大點(diǎn)難度, 如果在return語句還有代碼呢? 那句代碼會(huì)怎樣呢?
def test(): a = 2 return a s = 3 print s s = test() print s # 結(jié)果是什么?
老司機(jī)肯定一眼就能看出結(jié)果, 但是對(duì)于尚在入門或者對(duì)return不很了解的童鞋, 可能就會(huì)懵逼了~ 后面的兩句代碼是否會(huì)被執(zhí)行?
答案是: 不會(huì)執(zhí)行
return正如它的名字那樣, 當(dāng)執(zhí)行這句代碼, 整個(gè)函數(shù)都會(huì)返回, 整個(gè)調(diào)用就算結(jié)束了~ 所以在return后面的代碼, 都是不會(huì)被執(zhí)行的!
??也正因?yàn)檫@個(gè)特性, 所以有種編碼規(guī)范叫early return的編碼規(guī)范就被倡導(dǎo)
它的意思大概就是: 當(dāng)條件已經(jīng)滿足返回時(shí), 就馬上返回
舉個(gè)例子來說明:
def test(): a = 2 if a > 2: result = "more than" else: result = "less than" return result s = test() print s
上面的代碼應(yīng)該比較容易理解, 就是根據(jù)a的值, 來決定返回的result是什么. 這樣的編碼相信也是大部分童鞋喜歡用的, 因?yàn)檫@樣比較符合我們直覺, 然而, 這樣寫似乎有點(diǎn)浪費(fèi), 因?yàn)楫?dāng)?shù)谝粋€(gè)判斷結(jié)束了, 如果結(jié)果為真, 就應(yīng)該返回more than, 然后結(jié)束函數(shù), 否則肯定就是返回less than, 所以我們可以把代碼調(diào)整成這樣:
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
結(jié)果都是和第一個(gè)寫法是一樣的! 第一次看到這樣寫法的童鞋, 可能會(huì)覺得比較難以接受, 甚至覺得可讀性很差, 但是其實(shí)這樣的寫法, 我覺得反而會(huì)稍微好點(diǎn). 因?yàn)?
運(yùn)行的代碼數(shù)少了, 調(diào)用方能更快得到結(jié)果
有利于減少嵌套的層數(shù), 便于理解.
對(duì)于第2點(diǎn)在這需要解釋下, 很多時(shí)候我們寫得代碼, 嵌套很深, 都是因?yàn)?b>if/else的鍋, 因?yàn)榍短椎?b>if/else 比較多, 所以導(dǎo)致一堆代碼都嵌套得比較深, 這樣對(duì)于其他小伙伴, 簡直就是災(zāi)難, 因?yàn)樗麄兒芸赡茉陂喿x這部分代碼時(shí), 就忘了前面的邏輯....
為了更加容易理解, 舉個(gè)代碼例子:
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 # 輸出結(jié)果 test ~ test ~ Target !
代碼簡化優(yōu)化版:
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 # 輸出結(jié)果 test ~ test ~ Target !
這樣對(duì)比這來看, 應(yīng)該能更好地理解為什么說early return能夠減少嵌套的層數(shù)吧~ 有疑問歡迎留言討論~
談?wù)勆羁?/b>剛才花了比較長的篇幅去介紹return, 相信看到這里, 對(duì)于return應(yīng)該有比較基本的理解了! 所以來聊聊更加迷惑的話題:
當(dāng) return 遇上 try..finally, 會(huì)怎樣呢?
如果剛才有認(rèn)真看的話, 會(huì)注意到一句話, 就是:
return 代表整個(gè)函數(shù)返回, 函數(shù)調(diào)用算結(jié)束
但事實(shí)真的這樣嗎? 通常這樣問, 答案一般都不是 ~~
先來看看例子:
def test(): try: a = 2 return a except: pass finally: print "finally" s = test() print s
可以猜猜這句print a會(huì)不會(huì)打印? 相信很多童鞋都想了一會(huì), 然后說不會(huì)~ 然而這個(gè)答案是錯(cuò)的, 真正的輸出是:
finally 2
有木有覺得仿佛看見了新大陸, 在一開始的例子中, return后面的語句沒有被執(zhí)行, 但是在這里, 相隔那么遠(yuǎn), 卻依舊沒有忘記, 這或許就是"真愛"吧!
然而就是因?yàn)檫@種"真愛", 總是會(huì)讓一堆新老司機(jī)掉坑里..然后還不知道為毛..
為了避免它們?cè)倮^續(xù)借用打著"真愛"的幌子, 欺負(fù)我們, 讓我們一起來揭開這"真愛"的真面目!
于是, 我們得借助偷窺神器: dis, 想想都有點(diǎn)小興奮!
import dis def test(): try: a = 2 return a except: pass finally: print "finally" print dis.dis(test)
輸出比較長, 多帶帶寫:
# 輸出結(jié)果 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. 第一列是代碼在文件的行號(hào) 2. 第二列字節(jié)碼的偏移量 3. 字節(jié)碼的名字 4. 參數(shù) 5. 字節(jié)碼處理參數(shù)最終的結(jié)果
在字節(jié)碼中可以看到, 依次是SETUP_FINALLY 和 SETUP_EXCEPT, 這個(gè)對(duì)應(yīng)的就是finally和try,雖然finally在try后面, 雖然我們通常幫他們看成一個(gè)整體, 但是他們?cè)趯?shí)際上卻是分開的... 因?yàn)槲覀冎攸c(diǎn)是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 就是調(diào)用下PyFrame_BlockSetup去創(chuàng)建一個(gè)Block, 然后為這個(gè)Block設(shè)置:
b_type (opcode 也就是SETUP_FINALLY)
b_level
b_handler (INSTR_OFFSET() + oparg)
handler 可能比較難理解, 其實(shí)看剛才的 dis 輸出就能看到是哪個(gè), 就是 13 >> 31 LOAD_CONST 2 ("finally"), 這個(gè)箭頭就是告訴我們跳轉(zhuǎn)的位置的, 為什么會(huì)跳轉(zhuǎn)到這句呢? 因?yàn)?b>6 0 SETUP_FINALLY 28 (to 31)已經(jīng)告訴我們將要跳轉(zhuǎn)到31這個(gè)位置~~~
如果這個(gè)搞清楚了, 那就再來繼續(xù)看 return, return對(duì)應(yīng)的字節(jié)碼是: RETURN_VALUE, 所以它對(duì)應(yīng)的源碼是:
// ceval.c TARGET_NOARG(RETURN_VALUE) { retval = POP(); why = WHY_RETURN; goto fast_block_end; }
原來我們以前理解的return是假return! 這個(gè)return并沒有直接返回嘛, 而是將堆棧的值彈出來, 賦值個(gè)retval, 然后將why設(shè)置成WHY_RETURN, 接著就跑路了! 跑到一個(gè)叫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 */
在這需要回顧下剛才的一些知識(shí), 剛才我們看了return的代碼, 看到它將why設(shè)置成了 WHY_RETURN, 所以在這么一大串判斷中, 它只是走了最后面的else, 動(dòng)作也很簡單, 就是將剛才return儲(chǔ)存的值retval再push壓回棧, 同時(shí)將why轉(zhuǎn)換成long再壓回棧, 然后有設(shè)置了下why,接著就是屁顛屁顛去執(zhí)行剛才SETUP_FINALLY設(shè)置的b_handler代碼了~ 當(dāng)這這段bhandler代碼執(zhí)行完, 就再通過END_FINALLY去做回該做的事, 而這里就是, return retval
結(jié)論所以, 我們應(yīng)該能知道為什么當(dāng)我們執(zhí)行了return代碼, 為什么finally的代碼還會(huì)先執(zhí)行了吧, 因?yàn)?b>return的本質(zhì), 就是設(shè)置why和retval, 然后goto到一個(gè)大判斷, 最后根據(jù)why的值去執(zhí)行對(duì)應(yīng)的操作! 所以可以說并不是真的實(shí)質(zhì)性的返回. 希望我們往后再用到它們的時(shí)候, 別再掉坑里!
歡迎各位大神指點(diǎn)交流, QQ討論群: 258498217
轉(zhuǎn)載請(qǐng)注明來源: https://segmentfault.com/a/11...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/40740.html
摘要:本文用到的組件這是一個(gè)簡單版仿微信的左滑刪除的組件左滑刪除組件地址倉庫地址問題在上,使用仿微信樣式的左滑刪除組件時(shí)如果這一行的背景色是透明的,就會(huì)出現(xiàn)如下問題透明的下可以看到刪除和編輯按鈕,我們就需要處理一下。 本文用到的組件: 這是一個(gè)簡單版仿微信的左滑刪除的組件: # 左滑刪除組件 left_scroll_actions: any pub地址:https://pub.dartlan...
摘要:它有發(fā)布者,訂閱者這兩個(gè)主要對(duì)象。的最佳實(shí)踐就是通過反射犧牲了微小的性能,同時(shí)極大的降低了程序的耦合度。官網(wǎng)和應(yīng)用場景框架的主要功能是幫助我們來降低多個(gè)組件通信之間的耦合度的解耦。 前兩天在公眾號(hào)里發(fā)了一篇有關(guān)EventBus的文章《玩轉(zhuǎn)EventBus,詳解其使用》,有讀者和開發(fā)者反饋說沒有OTTO好用。確實(shí)是,各有優(yōu)缺點(diǎn)吧,那今天就有必要再講一下Otto事件框架。 OTTO是Squ...
摘要:類的屬性和構(gòu)造函數(shù)二的初始化構(gòu)造方法這是的構(gòu)造函數(shù)之一,其他構(gòu)造函數(shù)都引用這個(gè)構(gòu)造函數(shù)進(jìn)行初始化。在構(gòu)造函數(shù)中不會(huì)對(duì)數(shù)組進(jìn)行初始化,只有在等操作方法內(nèi)會(huì)進(jìn)行判斷是否要初始化或擴(kuò)容。其作用是保證的效率。 引言 HashMap在鍵值對(duì)存儲(chǔ)中被經(jīng)常使用,那么它到底是如何實(shí)現(xiàn)鍵值存儲(chǔ)的呢? 一 Entry Entry是Map接口中的一個(gè)內(nèi)部接口,它是實(shí)現(xiàn)鍵值對(duì)存儲(chǔ)關(guān)鍵。在HashMap中,有E...
摘要:懶漢非線程安全,需要用一定的風(fēng)騷操作控制,裝逼失敗有可能導(dǎo)致看一周的海綿寶寶餓漢天生線程安全,的時(shí)候就已經(jīng)實(shí)例化好,該操作過于風(fēng)騷會(huì)造成資源浪費(fèi)單例注冊(cè)表初始化的時(shí)候,默認(rèn)單例用的就是該方式特點(diǎn)私有構(gòu)造方法,只能有一個(gè)實(shí)例。 單例設(shè)計(jì)模式(Singleton Pattern)是最簡單且常見的設(shè)計(jì)模式之一,主要作用是提供一個(gè)全局訪問且只實(shí)例化一次的對(duì)象,避免多實(shí)例對(duì)象的情況下引起邏輯性錯(cuò)...
閱讀 3279·2021-11-22 14:44
閱讀 1124·2021-11-16 11:53
閱讀 1279·2021-11-12 10:36
閱讀 715·2021-10-14 09:43
閱讀 3708·2019-08-30 15:55
閱讀 3409·2019-08-30 14:14
閱讀 1747·2019-08-26 18:37
閱讀 3424·2019-08-26 12:12