要想玩裝飾器,那就得了解一下閉包,我們不得不這樣做!
閉包(Closure)
對象是附加了方法的數據。閉包是附帶數據的函數。
閉包是引用了自由變量的函數。大白話說,只要某個函數中引用了一些不在當前代碼全局中定義的變量,那麼這個函數就是閉包函數。
def f1(x):
x1 = x
def f2(y):
y = x1 + y
return y
return f2
In [1]: def f1(x):
...: x1 = x
...: def f2(y):
...: y = x1 + y
...: return y
...: return f2
...:
In [2]: fun = f1("你好")
In [3]: fun("世界")
Out[3]: '你好世界'
In [4]: f1("武漢")("加油")
Out[4]: '武漢加油'
In [5]: f1(f1("武漢")("加油"))("!!!")
Out[5]: '武漢加油!!!'
這是怎麼回事呢 ?
Python 中一切皆對象
fun = f1("你好")
中 fun
是指向 f1
函數返回的一個對象,也就是 f2
這個函數,通過傳遞參數 武漢
,fun
與以下代碼概念一致
def f2(y):
y = "武漢" + y
return y
當 fun("世界")
大家都明白,“你好世界”
f1("武漢")("加油")
是另一種寫法而已。
當調用 fun
時,f1
的作用域已經結束,x1
就成了孤鴻野鬼,它不在當前代碼全局中被定義,x1
是調用 fun
被使用的變量,所以 fun
是附帶數據的函數。
裝飾器
裝飾器語法只是一種語法糖,其返回值爲另一個函數的函數,通常使用 @wrapper
語法形式來進行函數變換。裝飾器通俗的講就是某一個函數在不改動其代碼的情況下而爲它增加額外的功能。
現在有一段代碼,它的功能是打印一些內容
def f1():
print("你好")
如果想爲其增加功能,打印內容中要包含當前時間。這不很簡單嗎!!!
import time
def f1():
print("你好")
print(time.ctime())
或
import time
def print_time():
print(time.ctime())
def f1():
print("你好")
print_time()
No !!! 這也很不Pythonic。
語法糖
import time
def print_time(f):
def print_content():
print(time.ctime())
return f()
return print_content
@print_time
def f1():
print("你好")
其實它和以下代碼原理相同
import time
def print_time(f):
def print_content():
print(time.ctime())
return f()
return print_content
def f1():
print("你好")
f1 = print_time(f1)
又是那句話 Python 中一切皆對象
f1
作爲參數傳遞給 print_time
,print_time
返回了內層函數 print_content
的引用重新賦給 f1
,而 print_content
函數內包含了函數 f1
的引用,也就是閉包所講的。
這個代碼使時間輸出在 f1
函數的上一行,如果想讓先執行 f1
的內容呢
import time
def print_time(f):
def print_content():
rlt = f()
print(time.ctime())
return rlt
return print_content
def f1():
print("你好")
f1 = print_time(f1)
如果被額外增加功能的函數有參數,那上面的方法就涼了,那怎麼辦?
讓其內層函數可以接收任意個參數就OK了
import time
def print_time(f):
def print_content(*args, **kwargs):
print(time.ctime())
return f(*args, **kwargs)
return print_content
@print_time
def f1(x=''):
print("你好" + str(x))
這要做無論被裝飾的函數是否有參數都不影響,豈不是美滋滋。
帶參數的裝飾器
import time
def fun(a):
def print_time(f):
def print_content(*args, **kwargs):
print(time.ctime())
print(a)
return f(*args, **kwargs)
return print_content
return print_time
@fun("朋友")
def f1(x=''):
print("你好" + str(x))
帶參數的裝飾器無非是外面有多了一層函數,用於接收參數
這也好理解,原理和以下代碼相同
import time
def fun(a):
def print_time(f):
def print_content(*args, **kwargs):
print(time.ctime())
print(a)
return f(*args, **kwargs)
return print_content
return print_time
def f1(x=''):
print("你好" + str(x))
f1 = fun("朋友")(f1)
當調用帶參數的 fun
函數時返回了 print_time
函數的引用,所以他變成了 f1 = print_time(f1)
,這和上面的內容一致。
使用場景
- 授權(Authorization)
from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return f(*args, **kwargs)
return decorated
- 日誌(Logging)
from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
"""Do some math."""
return x + x
result = addition_func(4)