Pytho 中閉包與裝飾器詳解

來源:互聯網
上載者:User
閉包(closure)是函數式編程的重要的文法結構。閉包也是一種組織代碼的結構,它同樣提高了代碼的可重複使用性。

如果在一個內嵌函數裡,對在外部函數內(但不是在全域範圍)的變數進行引用,那麼內嵌函數就被認為是閉包(closure)。

定義在外部函數內但由內建函式引用或者使用的變數稱為自由變數。

總結一下,建立一個閉包必須滿足以下幾點:

  • 1. 必須有一個內嵌函數

  • 2. 內嵌函數必須引用外部函數中的變數

  • 3. 外部函數的傳回值必須是內嵌函數

1.閉包使用樣本

先看一個閉包的例子:

In [10]: def func(name):    ...:     def in_func(age):    ...:         print 'name:',name,'age:',age    ...:     return in_func    ...: In [11]: demo = func('feiyu')In [12]: demo(19)name: feiyu age: 19

這裡當調用 func 的時候就產生了一個閉包——in_func,並且該閉包持有自由變數——name,因此這也意味著,當函數func的生命週期結束之後,name這個變數依然存在,因為它被閉包引用了,所以不會被回收。

python 的函數內,可以直接引用外部變數,但不能改寫外部變數,因此如果在閉包中直接改寫父函數的變數,就會發生錯誤。看以下樣本:

實現一個計數閉包的例子:

def counter(start=0):count = [start] def incr():count[0] += 1return countreturn incra = counter()print 'a:',aIn [32]: def counter(start=0):    ...:     count = start    ...:     def incr():    ...:         count += 1    ...:         return count    ...:     return incr    ...: In [33]: a = counter()In [35]: a()  #此處會報錯UnboundLocalError: local variable 'count' referenced before assignment

應該像下面這樣使用:

In [36]: def counter(start=0):    ...:     count = [start]    ...:     def incr():    ...:         count[0] += 1    ...:         return count    ...:     return incr    ...: In [37]: count = counter(5)In [38]: for i in range(10):    ...:     print count(),    ...:     [6] [7] [8] [9] [10] [11] [12] [13] [14] [15]

2.使用閉包的陷阱

In [1]: def create():   ...:     return [lambda x:i*x for i in range(5)]  #推導式產生一個匿名函數的列表   ...: In [2]: create()Out[2]: [<function __main__.<lambda>>, <function __main__.<lambda>>, <function __main__.<lambda>>, <function __main__.<lambda>>, <function __main__.<lambda>>]In [4]: for mul in create():   ...:     print mul(2)   ...:     88888

結果是不是很奇怪,這算是閉包使用中的一個陷阱吧!來看看為什嗎?

在上面的代碼當中,函數create返回一個list裡面儲存了4個函數變數,這4個函數都共同的引用了迴圈變數i, 也就是說它們共用著同一個變數ii是會改變的,當函數調用時,迴圈變數i已經是等於4了,因此4個函數返回的都是8。如果,需要在閉包使用迴圈變數的值的話,把迴圈變數作為閉包的預設參數或者是通過偏函數來實現。實現的原理也很簡單,就是當把迴圈變數當參數傳入函數時,會申請新的記憶體。範例程式碼如下:

In [5]: def create():   ...:         return [lambda x,i=i:i*x for i in range(5)]    ...: In [7]: for mul in create():   ...:     print mul(2)   ...:     02468

3,閉包與裝飾器

裝飾器就是一種的閉包的應用,只不過其傳遞的是函數:

def addb(func):def wrapper():return '<b>' + func() + '</b>'return wrapperdef addli(func):def wrapper():return '<li>' + func() + '</li>'return wrapper @addb         # 等同於 demo = addb(addli(demo)) @addli        # 等同於 demo = addli(demo)def demo():return 'hello world'print demo()    # 執行的是 addb(addku(demo))

在執行時,首先將demo函數傳遞給addli進行裝飾,然後將裝飾後的函數傳遞給addb進行裝飾。所以最後返回的結果是:

<b><li>hello world</li></b>

4.裝飾器中的陷阱

當你寫了一個裝飾器作用在某個函數上,這個函數的重要的元資訊比如名字、文檔字串、註解和參數簽名都會丟失。

def out_func(func):def wrapper():        func()return wrapper@out_funcdef demo():"""        this is  a demo.    """print 'hello world.'if __name__ == '__main__':    demo()print "__name__:",demo.__name__print "__doc__:",demo.__doc__

看結果:

hello world.__name__: wrapper__doc__: None

函數名字和文檔字串都變成了閉包的資訊。好在可以使用 functools 庫中的 @wraps 裝飾器來註解底層封裝函數。

from functools import wrapsdef out_func(func):    @wraps(func)def wrapper():        func()return wrapper

自己試試結果吧!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.