說說Python中的閉包

原文出處: cicaday

Python中的閉包不是一個一說就能明白的概念澈吨,但是隨著你往學習的深入考阱,無論如何你都需要去了解這么一個東西偶芍。

閉包的概念

我們嘗試從概念上去理解一下閉包。

在一些語言中挠进,在函數(shù)中可以(嵌套)定義另一個函數(shù)時沮明,如果內部的函數(shù)引用了外部的函數(shù)的變量辕坝,則可能產生閉包。閉包可以用來在一個函數(shù)與一組“私有”變量之間創(chuàng)建關聯(lián)關系荐健。在給定函數(shù)被多次調用的過程中酱畅,這些私有變量能夠保持其持久性琳袄。
—— 維基百科

用比較容易懂的人話說,就是當某個函數(shù)被當成對象返回時纺酸,夾帶了外部變量窖逗,就形成了一個閉包〔褪撸看例子滑负。

def make_printer(msg):
    def printer():
        print msg  # 夾帶私貨(外部變量)
    return printer  # 返回的是函數(shù),帶私貨的函數(shù)
 
printer = make_printer('Foo!')
printer()

支持將函數(shù)當成對象使用的編程語言用含,一般都支持閉包。比如Python, JavaScript帮匾。

如何理解閉包

閉包存在有什么意義呢啄骇?為什么需要閉包?

我個人認為瘟斜,閉包存在的意義就是它夾帶了外部變量(私貨)缸夹,如果它不夾帶私貨,它和普通的函數(shù)就沒有任何區(qū)別螺句。同一個的函數(shù)夾帶了不同的私貨虽惭,就實現(xiàn)了不同的功能。其實你也可以這么理解蛇尚,閉包和面向接口編程的概念很像芽唇,可以把閉包理解成輕量級的接口封裝。

接口定義了一套對方法簽名的約束規(guī)則取劫。

def tag(tag_name):
    def add_tag(content):
        return "<{0}>{1}</{0}>".format(tag_name, content)
    return add_tag
 
content = 'Hello'
 
add_tag = tag('a')
print add_tag(content)
# <a>Hello</a>
 
add_tag = tag('b')
print add_tag(content)
# <b>Hello</b>

在這個例子里匆笤,我們想要一個給content加tag的功能,但是具體的tag_name是什么樣子的要根據實際需求來定谱邪,對外部調用的接口已經確定炮捧,就是add_tag(content)。如果按照面向接口方式實現(xiàn)惦银,我們會先把add_tag寫成接口咆课,指定其參數(shù)和返回類型,然后分別去實現(xiàn)a和b的add_tag扯俱。

但是在閉包的概念中书蚪,add_tag就是一個函數(shù),它需要tag_name和content兩個參數(shù)蘸吓,只不過tag_name這個參數(shù)是打包帶走的善炫。所以一開始時就可以告訴我怎么打包,然后帶走就行库继。

上面的例子不太生動箩艺,其實在我們生活和工作中窜醉,閉包的概念也很常見。比如說手機撥號艺谆,你只關心電話打給誰榨惰,而不會去糾結每個品牌的手機是怎么實現(xiàn)的,用到了哪些模塊静汤。再比如去餐館吃飯琅催,你只要付錢就可以享受到服務,你并不知道那桌飯菜用了多少地溝油虫给。這些都可以看成閉包藤抡,返回來的是一些功能或者服務(打電話,用餐)抹估,但是這些功能使用了外部變量(天線缠黍,地溝油等等)。

你也可以把一個類實例看成閉包药蜻,當你在構造這個類時瓷式,使用了不同的參數(shù),這些參數(shù)就是閉包里的包语泽,這個類對外提供的方法就是閉包的功能贸典。但是類遠遠大于閉包,因為閉包只是一個可以執(zhí)行的函數(shù)踱卵,但是類實例則有可能提供很多方法廊驼。

何時使用閉包

其實閉包在Python中很常見,只不過你沒特別注意這就是一個閉包惋砂。比如Python中的裝飾器Decorator蔬充,假如你需要寫一個帶參數(shù)的裝飾器,那么一般都會生成閉包班利。

為什么饥漫?因為Python的裝飾器是一個固定的函數(shù)接口形式。它要求你的裝飾器函數(shù)(或裝飾器類)必須接受一個函數(shù)并返回一個函數(shù):

# how to define
def wrapper(func1):  # 接受一個callable對象
    return func2  # 返回一個對象罗标,一般為函數(shù)
    
# how to use
def target_func(args): # 目標函數(shù)
    pass
 
# 調用方式一庸队,直接包裹
result = wrapper(target_func)(args)
 
# 調用方式二,使用@語法闯割,等同于方式一
@wrapper
def target_func(args):
    pass
 
result = target_func()

那么如果你的裝飾器如果帶參數(shù)呢彻消?那么你就需要在原來的裝飾器上再包一層,用于接收這些參數(shù)宙拉。這些參數(shù)(私貨)傳遞到內層的裝飾器里后宾尚,閉包就形成了。所以說當你的裝飾器需要自定義參數(shù)時,一般都會形成閉包煌贴。(類裝飾器例外)

def html_tags(tag_name):
    def wrapper_(func):
        def wrapper(*args, **kwargs):
            content = func(*args, **kwargs)
            return "<{tag}>{content}</{tag}>".format(tag=tag_name, content=content)
        return wrapper
    return wrapper_
 
@html_tags('b')
def hello(name='Toby'):
    return 'Hello {}!'.format(name)
 
# 不用@的寫法如下
# hello = html_tag('b')(hello)
# html_tag('b') 是一個閉包御板,它接受一個函數(shù),并返回一個函數(shù)
 
print hello()  # <b>Hello Toby!</b>
print hello('world')  # <b>Hello world!</b>

關于裝飾器的更深入剖析牛郑,可以看我寫的另外一篇博客怠肋。

再深入一點

其實也不必太深入,理解這上面的概念淹朋,很多看起來頭疼的代碼也不過如此笙各。

下面讓我們來了解一下閉包的包到底長什么樣子。其實閉包函數(shù)相對與普通函數(shù)會多出一個closure的屬性础芍,里面定義了一個元組用于存放所有的cell對象杈抢,每個cell對象一一保存了這個閉包中所有的外部變量。

>>> def make_printer(msg1, msg2):
    def printer():
        print msg1, msg2
    return printer
>>> printer = make_printer('Foo', 'Bar')  # 形成閉包
 
>>> printer.__closure__   # 返回cell元組
(<cell at 0x03A10930: str object at 0x039DA218>, <cell at 0x03A10910: str object at 0x039DA488>)
 
>>> printer.__closure__[0].cell_contents  # 第一個外部變量
'Foo'
>>> printer.__closure__[1].cell_contents  # 第二個外部變量
‘Bar’

原理就是這么簡單仑性。

參考鏈接

https://www.the5fire.com/clos
http://stackoverflow.com/ques


PyChina將聯(lián)合JetBrain(出品PyCharm的公司)一起在北京舉辦一次Python沙龍活動春感。

時間:11月26日晚上19:00-21:00

地點:科技寺北新橋 北京市東城區(qū)東四北大街107號科林大廈B座107室(近北新橋地鐵站)

歡迎大家報名參加本次活動,特別需要志愿者來幫忙組織本次活動虏缸。

詳情請點擊此處

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市嫩实,隨后出現(xiàn)的幾起案子刽辙,更是在濱河造成了極大的恐慌,老刑警劉巖甲献,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宰缤,死亡現(xiàn)場離奇詭異,居然都是意外死亡晃洒,警方通過查閱死者的電腦和手機慨灭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來球及,“玉大人氧骤,你說我怎么就攤上這事〕砸” “怎么了筹陵?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長镊尺。 經常有香客問我朦佩,道長,這世上最難降的妖魔是什么庐氮? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任语稠,我火速辦了婚禮,結果婚禮上弄砍,老公的妹妹穿的比我還像新娘仙畦。我一直安慰自己输涕,他們只是感情好,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布议泵。 她就那樣靜靜地躺著占贫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪先口。 梳的紋絲不亂的頭發(fā)上型奥,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機與錄音碉京,去河邊找鬼厢汹。 笑死,一個胖子當著我的面吹牛谐宙,可吹牛的內容都是我干的烫葬。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼凡蜻,長吁一口氣:“原來是場噩夢啊……” “哼搭综!你這毒婦竟也來了?” 一聲冷哼從身側響起划栓,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤兑巾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后忠荞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒋歌,經...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年委煤,在試婚紗的時候發(fā)現(xiàn)自己被綠了堂油。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡碧绞,死狀恐怖府框,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情讥邻,我是刑警寧澤寓免,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站计维,受9級特大地震影響袜香,放射性物質發(fā)生泄漏。R本人自食惡果不足惜鲫惶,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一蜈首、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦欢策、人聲如沸吆寨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啄清。三九已至,卻和暖如春俺孙,著一層夾襖步出監(jiān)牢的瞬間辣卒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工睛榄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荣茫,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓场靴,卻偏偏與公主長得像啡莉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子旨剥,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內容