還是決定寫一篇關(guān)于python裝飾器的文章. 裝飾器實(shí)在是太常用,也太好用的東西了. 這篇文章會從函數(shù)開始, 一步步解釋裝飾器. 因?yàn)檠b飾器實(shí)際是也只是高階函數(shù)而已.
1. 函數(shù)
函數(shù)就比較基礎(chǔ)了, 就像一個黑箱, 傳入?yún)?shù), 出來返回值. 比如這樣
>>> def incr(num):
... return num + 1
...
>>> incr(21)
22
2. 函數(shù)內(nèi)部定義函數(shù)
python支持在函數(shù)內(nèi)部定義函數(shù), 比如:
def parent():
print("Printing from the parent() function")
def first_child():
print("Printing from the first_child() function")
def second_child():
print("Printing from the second_child() function")
second_child()
first_child()
這個也很好理解, 輸出結(jié)果是這樣的:
>>> parent()
Printing from the parent() function
Printing from the second_child() function
Printing from the first_child() function
在函數(shù)內(nèi)部定義的函數(shù), 作用域只在函數(shù)內(nèi)部. 當(dāng)然, 我們可以再來個復(fù)雜點(diǎn)的例子.
>>> def hello(name):
... def say_hello():
... print("Hello", name)
... say_hello()
...
>>> hello('world')
Hello world
注意, 這里在hello
函數(shù)里面定義了一個函數(shù) say_hello
, say_hello
這里面用到一個變量name, 是函數(shù)hello傳入的參數(shù), 這個在裝飾器中會用到. 大家自己理解下.
3. 一切皆對象
python中一切皆是對象, 所有的類, 生成器, 變量, 甚至函數(shù)也是一樣. 比如我們上面定義的incr
函數(shù):
>>> a = incr
>>> a
<function incr at 0x7f5485f77050>
>>> a(7)
8
>>>
那既然是對象, 就可以作為參數(shù)傳進(jìn)函數(shù)中, 比如說:
>>> def print_incr(fun, num):
... num = fun(num)
... print(num+10)
>>> print_incr(incr, 10)
21
同樣的道理, 函數(shù)既然可以作為參數(shù), 也可以作為返回值, 比如:
>>> def left_or_right(direction):
...
... def left():
... print('<-')
...
... def right():
... print('->')
...
... if direction == 'left':
... return left
...
... elif direction == 'right':
... return right
...
>>> left_or_right('left')
<function left at 0x7f5485f73cb0>
>>> fun = left_or_right('left')
>>> fun()
<-
4. 簡單的裝飾器
裝飾器聽得很多了, 那到底裝飾器是個什么東西? 聽起來這么高端? 其實(shí)很簡單, 我們玩?zhèn)€補(bǔ)全句子的文字游戲, 括號括起來的是定語
- 裝飾器是函數(shù).
- 裝飾器是 (參數(shù)是函數(shù), 返回值也是函數(shù)的) 函數(shù).
寫裝飾器的時候大家請一定牢記這兩句
那么我們開始寫第一個裝飾器了.
def decorator(func):
# 我們在函數(shù)內(nèi)部定義一個函數(shù)
def wrapper():
print('start')
# 這里調(diào)用一下我們傳進(jìn)來的函數(shù)
func()
print('end')
# 返回一個函數(shù)
return wrapper
好了, 這就是一個簡單的裝飾器了. 我們可以這樣去使用這個裝飾器.
>>> def hello():
... print('hello')
...
>>> say_hello = decorator(hello)
>>> say_hello
<function decorator.<locals>.wrapper at 0x7f5485f4df80>
>>> say_hello()
start
hello
end
>>>
這里的decorator就是裝飾器了, 傳入一個hello函數(shù), 返回一個新的函數(shù), 所以歸根到底就是個函數(shù). 一切皆對象, 所以返回函數(shù)和返回?cái)?shù)字, 返回類的實(shí)例, 都沒什么太大區(qū)別.
如果你認(rèn)真閱讀前面三個部分, 這里的裝飾器應(yīng)該沒有任何問題. 如果還是感覺到困惑, 再去理解一遍前面三點(diǎn).
5. 語法糖
如果我們做個這樣一個裝飾器, 每次調(diào)用都要調(diào)用一下, 這可不太優(yōu)雅, 于是python提供一種便捷的語法糖方式. 還是上面的這個裝飾器舉例子
def decorator(func):
# 我們在函數(shù)內(nèi)部定義一個函數(shù)
def wrapper():
print('start')
# 這里調(diào)用一下我們傳進(jìn)來的函數(shù)
func()
print('end')
# 返回一個函數(shù)
return wrapper
@decorator
def hello():
print('hello')
這時候我們再調(diào)用hello
>>> hello()
start
hello
end
所以, @decorator 的意思就是說, hello = decorator(hello) , 是不是裝飾器也沒有那么復(fù)雜?
6. 帶參數(shù)的函數(shù)的裝飾器
動手試一下, 如何寫這樣一個函數(shù)的裝飾器?
def add(a, b):
return a+b
如果你對前面的部分理解的比較清楚透徹, 你大概已經(jīng)實(shí)現(xiàn)出來了.
我們理清一下思路, 裝飾器就是一個函數(shù), 傳入函數(shù), 返回函數(shù), 那就是這樣子了
def decorator(fun):
def wrapper(a, b):
print('start')
t = fun(a,b)
print(t)
print('end')
return wrapper
>>> a = decorator(add)
>>> a
<function decorator.<locals>.wrapper at 0x7f5485f4d680>
>>> a(7, 8)的
start
15
end
7. 可帶參數(shù)的裝飾器
我們這里實(shí)現(xiàn)一個裝飾器功能:
給定兩個參數(shù), msg(string) 和 times(int), 對于被裝飾的函數(shù), 先輸出msg信息, 然后將fun運(yùn)行times次,
同理, 如果你對于前面理解的透徹, 這部分應(yīng)該寫出來了.
給點(diǎn)提示, 裝飾器是什么?
裝飾器是函數(shù), 參數(shù)是函數(shù), 返回值也是函數(shù).
那么我們的裝飾器能不能用別的函數(shù)返回回來呢? 當(dāng)然可以.
# 外面這個函數(shù)其實(shí)是用來接受參數(shù)的外殼
def deco(msg, times):
# 里面的這個函數(shù)其實(shí)才是我們最終的裝飾器
def wrapper(fun):
print(msg)
for i in range(times):
fun()
# 這里返回我們的裝飾器
return wrapper
>>> @deco(time=5)
... def hello():
... print('hello world')
...
hello world
hello world
hello world
hello world
hello world
8. 舉個例子
計(jì)數(shù)裝飾器: 計(jì)算函數(shù)運(yùn)行次數(shù)
import functools
def count_calls(func):
@functools.wraps(func)
def wrapper_count_calls(*args, **kwargs):
wrapper_count_calls.num_calls += 1
print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
return func(*args, **kwargs)
wrapper_count_calls.num_calls = 0
return wrapper_count_calls
@count_calls
def say_whee():
print("Whee!")
9. 寫在后面
裝飾器的用途實(shí)在很多很廣, 如果有興趣進(jìn)一步了解, 可以去python的裝飾器庫去看一下:
https://wiki.python.org/moin/PythonDecoratorLibrary
我一開始用裝飾器的時候也是一臉懵逼, 但是也堅(jiān)持去用, 只要能用到的地方, 都盡可能的去使用, 不會的地方就去查別人的代碼, 然后模仿著去寫, 慢慢會用了之后再回頭咀嚼原理直到吃透. 所以, 最重要的是多動手, 寫就完了.
寫了蠻久的, 如果對你有幫助, 順手點(diǎn)個贊(≧▽≦)/. 大佬們贊賞就更歡迎啦. 還在學(xué)習(xí), 如果有哪里不對的請多指教.