天天看點

try finally return python_Python :淺析 return 和 finally 共同挖的坑

原标題:Python :淺析 return 和 finally 共同挖的坑

來源:Lin_R

segmentfault.com/a/1190000010701665

初識 return

相信每一個用過Python函數的童鞋, 肯定會用過return語句, return顧名思義, 就是用來傳回值給調用者, 例如:

deftest():

a=2

returna

s=test()

prints

# 輸出結果

2

對于上面的結果, 相信大家都不會感到意外, 那麼加大點難度, 如果在return語句還有代碼呢? 那句代碼會怎樣呢?

deftest():

a=2

returna

s=3

prints

s=test()

prints

# 結果是什麼?

老司機肯定一眼就能看出結果, 但是對于尚在入門或者對return不很了解的童鞋, 可能就會懵逼了~ 後面的兩句代碼是否會被執行?

答案是: 不會執行

return正如它的名字那樣, 當執行這句代碼, 整個函數都會傳回, 整個調用就算結束了~ 是以在return後面的代碼, 都是不會被執行的!

也正因為這個特性, 是以有種編碼規範叫early return的編碼規範就被倡導。

它的意思大概就是: 當條件已經滿足傳回時, 就馬上傳回

舉個例子來說明:

deftest():

a=2

ifa>2:

result='more than'

else:

result='less than'

returnresult

s=test()

prints

上面的代碼應該比較容易了解, 就是根據a的值, 來決定傳回的result是什麼. 這樣的編碼相信也是大部分童鞋喜歡用的, 因為這樣比較符合我們直覺, 然而, 這樣寫似乎有點浪費, 因為當第一個判斷結束了, 如果結果為真, 就應該傳回more than, 然後結束函數, 否則肯定就是傳回less than, 是以我們可以把代碼調整成這樣:

deftest():

a=2

ifa>2:

return'more than'

else:

return'less than'

s=test()

prints

甚至是:

deftest():

a=2

ifa>2:

return'more than'

return'less than'

s=test()

prints

結果都是和第一個寫法是一樣的! 第一次看到這樣寫法的童鞋, 可能會覺得比較難以接受, 甚至覺得可讀性很差, 但是其實這樣的寫法, 我覺得反而會稍微好點. 因為:

運作的代碼數少了, 調用方能更快得到結果

有利于減少嵌套的層數, 便于了解.

對于第2點在這需要解釋下, 很多時候我們寫得代碼, 嵌套很深, 都是因為if/else的鍋, 因為嵌套的if/else 比較多, 是以導緻一堆代碼都嵌套得比較深, 這樣對于其他小夥伴, 簡直就是災難, 因為他們很可能在閱讀這部分代碼時, 就忘了前面的邏輯….

為了更加容易了解, 舉個代碼例子:

deftest():

a=2

ifa>2:

result='not 2'

else:

a+=2

ifa<2:

result='not 2'

else:

foriinrange(2):

print'test ~'

result='Target !'

returnresult

s=test()

prints

# 輸出結果

test~

test~

Target!

代碼簡化優化版:

deftest():

a=2

ifa>2:

return'not 2'

a+=2

ifa<2:

return'not 2'

foriinrange(2):

print'test ~'

return'Target !'

s=test()

prints

# 輸出結果

test~

test~

Target!

這樣對比這來看, 應該能更好地了解為什麼說early return能夠減少嵌套的層數吧~ 有疑問歡迎留言讨論~

談談深坑

剛才花了比較長的篇幅去介紹return, 相信看到這裡, 對于return應該有比較基本的了解了! 是以來聊聊更加迷惑的話題:

當 return 遇上 try..finally, 會怎樣呢?

如果剛才有認真看的話, 會注意到一句話, 就是:

return 代表整個函數傳回, 函數調用算結束

但事實真的這樣嗎? 通常這樣問, 答案一般都不是 ~~

先來看看例子:

deftest():

try:

a=2

returna

except:

pass

finally:

print'finally'

s=test()

prints

可以猜猜這句print a會不會列印? 相信很多童鞋都想了一會, 然後說不會~ 然而這個答案是錯的, 真正的輸出是:

finally

2

有木有覺得仿佛看見了新大陸, 在一開始的例子中, return後面的語句沒有被執行, 但是在這裡, 相隔那麼遠, 卻依舊沒有忘記, 這或許就是”真愛”吧!

然而就是因為這種”真愛”, 總是會讓一堆新老司機掉坑裡..然後還不知道為毛..

為了避免它們再繼續借用打着”真愛”的幌子, 欺負我們, 讓我們一起來揭開這”真愛”的真面目!

于是, 我們得借助偷窺神器: dis, 想想都有點小興奮!

importdis

deftest():

try:

a=2

returna

except:

pass

finally:

print'finally'

printdis.dis(test)

輸出比較長, 單獨寫:

# 輸出結果

60SETUP_FINALLY28(to31)

3SETUP_EXCEPT14(to20)

76LOAD_CONST1(2)

9STORE_FAST0(a)

812LOAD_FAST0(a)

15RETURN_VALUE

16POP_BLOCK

17JUMP_FORWARD7(to27)

9>>20POP_TOP

21POP_TOP

22POP_TOP

1023JUMP_FORWARD1(to27)

26END_FINALLY

>>27POP_BLOCK

28LOAD_CONST0(None)

13>>31LOAD_CONST2('finally')

34PRINT_ITEM

35PRINT_NEWLINE

36END_FINALLY

37LOAD_CONST0(None)

40RETURN_VALUE

這邊簡單說着這些列所代表的意思:

第一列是代碼在檔案的行号

第二列位元組碼的偏移量

位元組碼的名字

參數

位元組碼處理參數最終的結果

在位元組碼中可以看到, 依次是SETUP_FINALLY 和 SETUP_EXCEPT, 這個對應的就是finally和try,雖然finally在try後面, 雖然我們通常幫他們看成一個整體, 但是他們在實際上卻是分開的… 因為我們重點是finally, 是以就單單看SETUP_FINALLY

//ceval.c

TARGET(SETUP_FINALLY)

_setup_finally:

{

PyFrame_BlockSetup(f,opcode,INSTR_OFFSET()+oparg,

STACK_LEVEL());

DISPATCH();

}

//fameobject.c

void

PyFrame_BlockSetup(PyFrameObject*f,inttype,inthandler,intlevel)

{

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;

gotofast_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){

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;

}

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);

}

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;

}

}

在這需要回顧下剛才的一些知識, 剛才我們看了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的值去執行對應的操作! 是以可以說并不是真的實質性的傳回. 希望我們往後再用到它們的時候, 别再掉坑裡!傳回搜狐,檢視更多

責任編輯: