Python進階(二)

博客鏈接:http://inarrater.com/2016/07/03/pythonadvance2/

這一部分是關于Python的Callable。在Stackoverflow上有一個專門的問題叫做“What is a "callable" in Python”,高票回答中說:

A callable is anything that can be called.

這個回答很抽象,大雄從更具體的角度來闡述Callable這個概念——在Python中哪些是callable的?

  • function
  • closure
  • bound method
  • unbound method
  • class method
  • static method
  • functor
  • operator
  • class

先說答案,很明顯,列出的這些都是callable的。這些概念中的大部分我在工作中都有使用,包括比如closure的坑也幫助新同學調試bug的時候看到新入職的同學自己踩到過,但是對于bound methodunbound method這些概念還不是很清晰。我們也一個個來看。

3. Closure

Closure,閉包,在Python中本質上是一個函數,或者更具體來說它和Function的區別是它包含了Code和Environment,而Python中Environment又可以分為globals、locals和cells三部分。
globals和locals比較容易理解,其實就是兩個dict,分別保存了全局變量和局部變量,那這個cells是什么?我們先來看一個非常經典的例子:

def foo():
    logout_lst = []

    for i in xrange(5):
        def logout():
            print i
        logout_lst.append(logout)

    for l in logout_lst:
        l()

foo()

思考:這段代碼的輸出是什么?

分析一下這段代碼,雖然這里為了方便演示,構造了一個只有print的邏輯,你可能會質疑它的作用,但是在我們開發的過程中,就有同學在循環內部定義了類似的閉包用于引擎回調的調用,引用了外部了一個類似i的變量。例子中,在foo的函數內部,代碼def logout()定義了一個閉包(寫到這里讓我想起了遙遠的過去寫JAVA代碼時使用的Inner Class),然后我們想使用外部變量i的值,這里只是把它輸出出來,通常我們想要輸出的結果是打印0、1、2、3、4這幾個數字,當然中間有換行,但是最終的輸出結果是什么呢?
5個4!
為什么呢?我們來添加一些輸出日志來查看一下,為了方便看輸出,我們只循環兩次來看,修改后的代碼如下:

def foo():
    logout_lst = []

    for i in xrange(2):
        def logout():
            print "i:", i, id(i)
            print "globals:", globals()
            print "locals:", locals()
        logout_lst.append(logout)

    for l in logout_lst:
        l()
        print "Cells:", l.__closure__, id(l.__closure__[0].cell_contents)
        print ''

foo()

輸出的結果如下:

i: 1 35882616
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x022C72B0>, '__doc__': None}
locals: {'i': 1}
Cells: (<cell at 0x02354570: int object at 0x02238678>,) 35882616

i: 1 35882616
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x022C72B0>, '__doc__': None}
locals: {'i': 1}
Cells: (<cell at 0x02354570: int object at 0x02238678>,) 35882616

首先打印一下i的值與i這個變量的id,你可以認為這是i在Python虛擬機中的唯一編號,兩次輸出它的值都是1,id也都是一個35882616,然后輸出一下globals和locals看一下,這兩個很簡單,不做分析了。最后通過__closure屬性來看下閉包的內容:

Cells: (<cell at 0x02354570: int object at 0x02238678>,)

這就是前面說的cells,它是一個cell對象,里面的內容有一個int對象,通過cell_contents屬性可以查看到它的id是35882616,和i是一樣的。
可以看出,cells就是對于up-values的引用(references)注意引用!
那之前的輸出就很容易理解了,引用,當后面調用閉包執行的時候,i變量值已經變成了4,那輸出i自然每次都是4。
最后,如何修改可以讓你的代碼可以按照之前的計劃正常執行呢?很簡單,不要直接使用cells中的值,而是用一個參數來讓它變成參數,就是定義這個閉包的時刻的值了。

def foo():
    logout_lst = []

    for i in xrange(2):
        def logout(x = i):
            print "x:", x, id(x)
            print "globals:", globals()
            print "locals:", locals()
        logout_lst.append(logout)

    for l in logout_lst:
        l()
        print "Cells:", l.__closure__
        print ''

foo()

輸出結果:

x: 0 37062276
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x023E72B0>, '__doc__': None}
locals: {'x': 0}
Cells: None

x: 1 37062264
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x023E72B0>, '__doc__': None}
locals: {'x': 1}
Cells: None

此處,cells的內容變為了None,輸出的結果也是0和1,它們的id自然也不同。其實參數也可以寫成def logout(i = i):,內部可以使用i,但是這會造成一些困擾,個人不推薦這么寫。

思考:那么你以為這個坑就踩完了嗎?有沒有哪里還可能存在問題?

def logout(x = i):這種定義雖然用在閉包里,但是其實是函數的默認參數,那么默認參數如果使用list、dict或者python object等這樣mutable的值會怎樣?這自然是另外一個入門級的坑:

背景: 不建議在函數默認參數中使用mutable value,而保證只使用immutable value。

但有時候為了解決一個坑,可能不小心踩入另外一個坑。如果這里使用了,比如一個list對象作為參數,那么創建出來的這幾個閉包中的x都引用的會是同一個對象,而且,在任何一個閉包多次調用的時候,x的值都是同一個對象的引用。如果像例子中是只讀的邏輯的話,可能沒有問題,如果后面有人添加了修改的邏輯,那就呵呵呵呵了。可能會亂成一鍋粥,出現各種神奇的現象,寫這樣邏輯的人自求多福吧。

總結:理解閉包的概念,理解引用的概念,編寫代碼保持思路清晰,明確自己使用的變量存在在哪里,是一件非常非常重要的事情,對團隊開發中避免匪夷所思令人抓狂的Bug很有幫助!

這一部分只講閉包這一個點,其實關于閉包還有很多知識點,有興趣的可以自己查閱相關資料。第三部分講解bound method和unbound method,這是我這次課程最喜歡的部分。

PS: 很多坑,你看過文章介紹,或者聽同事講過,但是寫代碼的時候有時還是會由于當時思路的混亂而饒進去,重新踩一遍,這往往難以避免,不親身經歷的坑思維上很難那么敏感。經驗學習和知識積累的作用,是讓你從坑中往外爬的時候更快一些,回頭看那些坑印象更深刻一些。

2016年7月2日于杭州網易大廈

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容

  • 個人筆記,方便自己查閱使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik閱讀 67,743評論 0 5
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,829評論 18 139
  • 以下翻譯自Apple官方文檔,結合自己的理解記錄下來。翻譯基于 swift 3.0.1 原文地址 Closure...
    藝術農閱讀 1,584評論 0 3
  • 顧客永遠是對的,學員也是我們的顧客,但是我們要有這么一個心態:錯的,永遠是學員。 學員和我們一樣!都...
    蜀通駕校張國安閱讀 660評論 0 0
  • 1.MRC環境下橋接 - (void)MRC{ //MRC下橋接 //Foundation到CoreFoundat...
    流氓兔劉閱讀 232評論 0 0