python 閉包和裝飾器

xie仗劍天涯發表於2017-06-01

python 閉包和裝飾器

一、閉包
閉包:外部函式FunOut()裡面包含一個內部函式FunIn(),並且外部函式返回內部函式的物件FunIn,內部函式存在對外部函式的變數的引用。那麼這個內部函式FunIn就叫做閉包。你在呼叫函式FunA的時候傳遞的引數就是自由變數

# 例項1

def FunOut(name): #外部函式包含內部函式
def FunIn(age):
print ("name:",name,"age:",age) # 內部函式存在對外部函式變數的引用,如name
return FunIn # 返回內部函式物件FunIn
FunIn = FunOut("Alian") # Alian傳給引數name,並且返回函式FunIn物件給Funin
FunIn(25) # 此時Funin相當於呼叫函式FunIn,因此25傳給引數age
# ('name:', 'Alian', 'age:', 25)

解析:裡面呼叫FunOut()的時候就產生了一個閉包FunIn,並且該閉包持有自由變數name,因此這也意味著,當函式FunOut()的生命週期結束之後,自由變數name依然存在,因為它被閉包引用了,所以不會被回收。

#例項2

def line_conf(a,b):
def line(x):
return a * x + b
return line

line1 = line_conf(1,1)
line2 = line_conf(4,5)
print (line1(5),line2(5))
# (6, 25)

解析:函式line與環境變數a,b構成閉包。在建立閉包的時候,我們通過line_conf的引數a,b說明了這兩個環境變數的取值,這樣,我們就確定了函式的最終形式(y = x + 1和y = 4x + 5)。我們只需要變換引數a,b,就可以獲得不同的直線表達函式

閉包作用:閉包也具有提高程式碼可複用性的作用


二、裝飾器
裝飾器概念: 裝飾器本質上也是一個Python函式,它可以讓其他函式在不需要做任何程式碼變動的前提下增加額外功能,裝飾器的返回值也是一個函式物件。裝飾器接受其他函式為引數並返回一個裝飾過的函式(或其他物件)。

由於函式也是一個物件,而且函式物件可以被賦值給變數,所以,通過變數也能呼叫該函式。

def now():
print ("2017-05-31")

f = now
f()
# 2017-05-31

函式物件有一個__name__屬性,可以拿到函式的名字:

now.__name__
# 'now'
f.__name__
# 'now'

  

假設我們要增強now()函式的功能,比如,在函式呼叫前後自動列印日誌,但又不希望修改now()函式的定義,這種在程式碼執行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)

本質上,decorator就是一個返回函式的高階函式。所以,我們要定義一個能列印日誌的decorator,可以定義如下:

def log(func):
def wrapper(*args,**kw):
print ("call %s():"%func.__name__)
return func(*args,**kw)
return wrapper



觀察上面的log,因為它是一個decorator,所以接受一個函式作為引數,並返回一個函式。我們要藉助Python的@語法,把decorator置於函式的定義處:

@log
def now():
print ("2017-05-31")

  

呼叫now()函式,不僅會執行now()函式本身,還會在執行now()函式前列印一行日誌:

now()
# call now():
# 2017-05-31

  

把@log放到now()函式的定義處,相當於執行了語句:

now = log(now)

  

由於log()是一個decorator,返回一個函式,所以,原來的now()函式仍然存在,只是現在同名的now變數指向了新的函式,於是呼叫now()將執行新函式,即在log()函式中返回的wrapper()函式。

wrapper()函式的引數定義是(*args, **kw),因此,wrapper()函式可以接受任意引數的呼叫。在wrapper()函式內,首先列印日誌,再緊接著呼叫原始函式

如果decorator本身需要傳入引數,那就需要編寫一個返回decorator的高階函式,寫出來會更復雜。比如,要自定義log的文字:

def log(text):
def decorator(func):
def wrapper(*args,**kw):
print ('%s %s():' % (text, func.__name__))
return func(*args,**kw)
return wrapper
return decorator



這個3層巢狀的decorator用法如下:

@log("execute")
def now():
print ("2017-05-31")

  

執行結果:

now()
# execute now():
# 2017-05-31

解析:首先執行log('execute'),返回的是decorator函式,再呼叫返回的函式,引數是now函式,返回值最終是wrapper函式

和兩層巢狀的decorator相比,3層巢狀的效果是這樣的:

now = log ("execute")(now)


以上兩種decorator的定義都沒有問題,但還差最後一步。因為我們講了函式也是物件,它有__name__等屬性,但你去看經過decorator裝飾之後的函式,它們的__name__已經從原來的'now'變成了'wrapper':

因為返回的那個wrapper()函式名字就是'wrapper',所以,需要把原始函式的__name__等屬性複製到wrapper()函式中,否則,有些依賴函式簽名的程式碼執行就會出錯。

不需要編寫wrapper.__name__ = func.__name__這樣的程式碼,Python內建的functools.wraps就是幹這個事的,所以,一個完整的decorator的寫法如下:

import functools
def log(func):
@functools.wraps(func)
def wrapper(*args,**kw):
print ('call %s():' % func.__name__)
return func(*args,**kw)
return wrapper

  

# 或者針對帶引數的decorator:

import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print ('%s %s():' % (text, func.__name__))
return func(*args,**kw)
return wrapper
return decorator

 

 

參考:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000


 

相關文章