首頁 >後端開發 >Python教學 >Pytho 中閉包與裝飾器詳解

Pytho 中閉包與裝飾器詳解

零下一度
零下一度原創
2017-06-25 09:55:031406瀏覽

閉包(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 incr

a = 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 &#39;<b>&#39; + func() + &#39;</b>&#39;return wrapperdef addli(func):def wrapper():return &#39;<li>&#39; + func() + &#39;</li>&#39;return wrapper 

@addb         # 等同于 demo = addb(addli(demo)) 
@addli        # 等同于 demo = addli(demo)def demo():return &#39;hello world&#39;

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 &#39;hello world.&#39;if __name__ == &#39;__main__&#39;:
    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

自己試試看結果吧!

以上是Pytho 中閉包與裝飾器詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn