亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺析Python中return和finally共同挖的坑

 更新時(shí)間:2017年08月18日 10:17:33   作者:Lin_R  
最近在工作中遇到一個(gè)坑,發(fā)現(xiàn)這個(gè)坑居然存在于return和finally,所以覺著有必要總結(jié)分享一下,下面這篇文章主要介紹了關(guān)于Python中return和finally共同挖的坑,需要的朋友可以參考借鑒,下面來一起看看吧。

前言

本文主要給大家介紹了在Python中return和finally共同存在的坑,以及填坑經(jīng)驗(yàn),分享出來供大家參考學(xué)習(xí),下面話不多說了,來一起看看詳細(xì)的介紹吧。

初識(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)閕f/else的鍋, 因?yàn)榍短椎膇f/else 比較多, 所以導(dǎo)致一堆代碼都嵌套得比較深, 這樣對(duì)于其他小伙伴, 簡(jiǎn)直就是災(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 !

代碼簡(jiǎn)化優(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ù)勆羁?/strong>

剛才花了比較長(zhǎng)的篇幅去介紹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)

輸出比較長(zhǎng), 單獨(dú)寫:

# 輸出結(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 

這邊簡(jiǎn)單說著這些列所代表的意思:

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)? 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)作也很簡(jiǎn)單, 就是將剛才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)閞eturn的本質(zhì), 就是設(shè)置why和retval, 然后goto到一個(gè)大判斷, 最后根據(jù)why的值去執(zhí)行對(duì)應(yīng)的操作! 所以可以說并不是真的實(shí)質(zhì)性的返回. 希望我們往后再用到它們的時(shí)候, 別再掉坑里!

好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • python腳本監(jiān)控Tomcat服務(wù)器的方法

    python腳本監(jiān)控Tomcat服務(wù)器的方法

    這篇文章主要介紹了利用python腳本監(jiān)控Tomcat服務(wù)器的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-07-07
  • Django-simple-captcha驗(yàn)證碼包使用方法詳解

    Django-simple-captcha驗(yàn)證碼包使用方法詳解

    這篇文章主要介紹了Django-simple-captcha驗(yàn)證碼包使用方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-11-11
  • python連接字符串的方法小結(jié)

    python連接字符串的方法小結(jié)

    這篇文章主要介紹了python連接字符串的方法,實(shí)例總結(jié)了幾種常用的Python連接字符串的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07
  • Python Json數(shù)據(jù)文件操作原理解析

    Python Json數(shù)據(jù)文件操作原理解析

    這篇文章主要介紹了Python Json數(shù)據(jù)文件操作原理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • Python之tkinter文字區(qū)域Text使用及說明

    Python之tkinter文字區(qū)域Text使用及說明

    這篇文章主要介紹了Python之tkinter文字區(qū)域Text使用及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • Python時(shí)間序列缺失值的處理方法(日期缺失填充)

    Python時(shí)間序列缺失值的處理方法(日期缺失填充)

    這篇文章主要給大家介紹了關(guān)于Python時(shí)間序列缺失值(日期缺失填充)的處理方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Python具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • 關(guān)于Word2Vec可視化展示

    關(guān)于Word2Vec可視化展示

    這篇文章主要介紹了關(guān)于Word2Vec可視化展示,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • python爬取Ajax動(dòng)態(tài)加載網(wǎng)頁過程解析

    python爬取Ajax動(dòng)態(tài)加載網(wǎng)頁過程解析

    這篇文章主要介紹了python爬取Ajax動(dòng)態(tài)加載網(wǎng)頁過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • 用python實(shí)現(xiàn)域名資產(chǎn)監(jiān)控的詳細(xì)步驟

    用python實(shí)現(xiàn)域名資產(chǎn)監(jiān)控的詳細(xì)步驟

    域名資產(chǎn)監(jiān)控,通過輸入一個(gè)主域名,找到該域名對(duì)應(yīng)的ip地址所在的服務(wù)器的端口開閉情況,本文重點(diǎn)給大家介紹用python實(shí)現(xiàn)域名資產(chǎn)監(jiān)控的問題,需要的朋友可以參考下
    2021-11-11
  • python中delattr刪除對(duì)象方法的代碼分析

    python中delattr刪除對(duì)象方法的代碼分析

    在本篇文章里小編給大家分享了一篇關(guān)于python中delattr刪除對(duì)象方法的代碼分析內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。
    2020-12-12

最新評(píng)論