Python 筆記 | 從零開始,理解python裝飾器

還是決定寫一篇關(guān)于python裝飾器的文章. 裝飾器實(shí)在是太常用,也太好用的東西了. 這篇文章會從函數(shù)開始, 一步步解釋裝飾器. 因?yàn)檠b飾器實(shí)際是也只是高階函數(shù)而已.

1. 函數(shù)

函數(shù)就比較基礎(chǔ)了, 就像一個(gè)黑箱, 傳入?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()

這個(gè)也很好理解, 輸出結(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)然, 我們可以再來個(gè)復(fù)雜點(diǎn)的例子.

>>> def hello(name):
...     def say_hello():
...         print("Hello", name)
...     say_hello()
... 
>>> hello('world')
Hello world

注意, 這里在hello函數(shù)里面定義了一個(gè)函數(shù) say_hello , say_hello這里面用到一個(gè)變量name, 是函數(shù)hello傳入的參數(shù), 這個(gè)在裝飾器中會用到. 大家自己理解下.

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. 簡單的裝飾器

裝飾器聽得很多了, 那到底裝飾器是個(gè)什么東西? 聽起來這么高端? 其實(shí)很簡單, 我們玩?zhèn)€補(bǔ)全句子的文字游戲, 括號括起來的是定語

  1. 裝飾器是函數(shù).
  2. 裝飾器是 (參數(shù)是函數(shù), 返回值也是函數(shù)的) 函數(shù).

寫裝飾器的時(shí)候大家請一定牢記這兩句
那么我們開始寫第一個(gè)裝飾器了.

def decorator(func):
   # 我們在函數(shù)內(nèi)部定義一個(gè)函數(shù)
   def wrapper():
        print('start')
        # 這里調(diào)用一下我們傳進(jìn)來的函數(shù)
        func()
        print('end')
   #  返回一個(gè)函數(shù)
   return wrapper

好了, 這就是一個(gè)簡單的裝飾器了. 我們可以這樣去使用這個(gè)裝飾器.

>>> def hello():
...     print('hello')
... 
>>> say_hello = decorator(hello)
>>> say_hello
<function decorator.<locals>.wrapper at 0x7f5485f4df80>
>>> say_hello()
start
hello
end
>>> 

這里的decorator就是裝飾器了, 傳入一個(gè)hello函數(shù), 返回一個(gè)新的函數(shù), 所以歸根到底就是個(gè)函數(shù). 一切皆對象, 所以返回函數(shù)和返回?cái)?shù)字, 返回類的實(shí)例, 都沒什么太大區(qū)別.
如果你認(rèn)真閱讀前面三個(gè)部分, 這里的裝飾器應(yīng)該沒有任何問題. 如果還是感覺到困惑, 再去理解一遍前面三點(diǎn).

5. 語法糖

如果我們做個(gè)這樣一個(gè)裝飾器, 每次調(diào)用都要調(diào)用一下, 這可不太優(yōu)雅, 于是python提供一種便捷的語法糖方式. 還是上面的這個(gè)裝飾器舉例子

def decorator(func):
   # 我們在函數(shù)內(nèi)部定義一個(gè)函數(shù)
   def wrapper():
        print('start')
        # 這里調(diào)用一下我們傳進(jìn)來的函數(shù)
        func()
        print('end')
   #  返回一個(gè)函數(shù)
   return wrapper

@decorator
def hello():
    print('hello')

這時(shí)候我們再調(diào)用hello

>>> hello()
start
hello
end

所以, @decorator 的意思就是說, hello = decorator(hello) , 是不是裝飾器也沒有那么復(fù)雜?

6. 帶參數(shù)的函數(shù)的裝飾器

動(dòng)手試一下, 如何寫這樣一個(gè)函數(shù)的裝飾器?

def add(a, b):
    return a+b

如果你對前面的部分理解的比較清楚透徹, 你大概已經(jīng)實(shí)現(xiàn)出來了.

我們理清一下思路, 裝飾器就是一個(gè)函數(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)一個(gè)裝飾器功能:
給定兩個(gè)參數(shù), msg(string) 和 times(int), 對于被裝飾的函數(shù), 先輸出msg信息, 然后將fun運(yùn)行times次,

同理, 如果你對于前面理解的透徹, 這部分應(yīng)該寫出來了.

給點(diǎn)提示, 裝飾器是什么?
裝飾器是函數(shù), 參數(shù)是函數(shù), 返回值也是函數(shù).

那么我們的裝飾器能不能用別的函數(shù)返回回來呢? 當(dāng)然可以.


#  外面這個(gè)函數(shù)其實(shí)是用來接受參數(shù)的外殼
def deco(msg, times):
    # 里面的這個(gè)函數(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. 舉個(gè)例子

計(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

我一開始用裝飾器的時(shí)候也是一臉懵逼, 但是也堅(jiān)持去用, 只要能用到的地方, 都盡可能的去使用, 不會的地方就去查別人的代碼, 然后模仿著去寫, 慢慢會用了之后再回頭咀嚼原理直到吃透. 所以, 最重要的是多動(dòng)手, 寫就完了.

寫了蠻久的, 如果對你有幫助, 順手點(diǎn)個(gè)贊(≧▽≦)/. 大佬們贊賞就更歡迎啦. 還在學(xué)習(xí), 如果有哪里不對的請多指教.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 部分細(xì)節(jié)自己改了點(diǎn),也加了點(diǎn)自己例子,基本上屬于轉(zhuǎn)載。轉(zhuǎn)載出處:https://my.oschina.net/le...
    洛克黃瓜閱讀 2,100評論 0 3
  • 要點(diǎn): 函數(shù)式編程:注意不是“函數(shù)編程”,多了一個(gè)“式” 模塊:如何使用模塊 面向?qū)ο缶幊蹋好嫦驅(qū)ο蟮母拍睢傩浴?..
    victorsungo閱讀 1,695評論 0 6
  • Python的裝飾器(decorator)是一個(gè)很棒的機(jī)制,也是熟練運(yùn)用Python的必殺技之一。裝飾器,顧名思義...
    溫柔的傾訴閱讀 443評論 0 0
  • 一、裝飾器的基本使用 在不改變函數(shù)源代碼的前提下,給函數(shù)添加新的功能,這時(shí)就需要用到“裝飾器”。 0.開放封閉原則...
    NJingZYuan閱讀 615評論 0 0
  • 在灰藍(lán)的宿舍里,住著四位性格迥異的小仙女,他們有著開朗樂觀的性子,也有著溫柔體貼的時(shí)候。就這樣彼此和諧相處,雖然有...
    木莓橙閱讀 917評論 0 1

友情鏈接更多精彩內(nèi)容