在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ 裝飾器
基礎
itertools
HTTP 服務
hashlib
閉包
文件和目錄
單元測試
使用 @property
標準模塊
陌生的 metaclass
Base64
進程、線程和協(xié)程
讀寫二進制文件
匿名函數(shù)
輸入和輸出
Click
元組
字符編碼
partial 函數(shù)
參考資料
collections
協(xié)程
類和實例
Python 之旅
定制類和魔法方法
常用數(shù)據類型
繼承和多態(tài)
ThreadLocal
HTTP 協(xié)議簡介
Requests 庫的使用
讀寫文本文件
列表
os 模塊
迭代器 (Iterator)
正則表達式
集合
上下文管理器
異常處理
你不知道的 super
定義函數(shù)
datetime
資源推薦
字典
slots 魔法
hmac
第三方模塊
進程
類方法和靜態(tài)方法
函數(shù)參數(shù)
高階函數(shù)
函數(shù)
re 模塊
高級特性
線程
argparse
生成器
結束語
字符串
map/reduce/filter
函數(shù)式編程
Celery
裝飾器

裝飾器

我們知道,在 Python 中,我們可以像使用變量一樣使用函數(shù):

  • 函數(shù)可以被賦值給其他變量
  • 函數(shù)可以被刪除
  • 可以在函數(shù)里面再定義函數(shù)
  • 函數(shù)可以作為參數(shù)傳遞給另外一個函數(shù)
  • 函數(shù)可以作為另一個函數(shù)的返回

簡而言之,函數(shù)就是一個對象

對一個簡單的函數(shù)進行裝飾

為了更好地理解裝飾器,我們先從一個簡單的例子開始,假設有下面的函數(shù):

def hello():
    return 'hello world'

現(xiàn)在我們想增強 hello() 函數(shù)的功能,希望給返回加上 HTML 標簽,比如 <i>hello world</i>,但是有一個要求,不改變原來 hello() 函數(shù)的定義。這里當然有很多種方法,下面給出一種跟本文相關的方法:

def makeitalic(func):
    def wrapped():
        return "<i>" + func() + "</i>"
    return wrapped

在上面的代碼中,我們定義了一個函數(shù) makeitalic,該函數(shù)有一個參數(shù) func,它是一個函數(shù);在 makeitalic 函數(shù)里面我們又定義了一個內部函數(shù) wrapped,并將該函數(shù)作為返回。

現(xiàn)在,我們就可以不改變 hello() 函數(shù)的定義,給返回加上 HTML 標簽了:

>>> hello = makeitalic(hello)  # 將 hello 函數(shù)傳給 makeitalic
>>> hello()
'<i>hello world</i>'

在上面,我們將 hello 函數(shù)傳給 makeitalic,再將返回賦給 hello,此時調用 hello() 就得到了我們想要的結果。

不過要注意的是,由于我們將 makeitalic 的返回賦給了 hello,此時 hello() 函數(shù)仍然存在,但是它的函數(shù)名不再是 hello 了,而是 wrapped,正是 makeitalic 返回函數(shù)的名稱,可以驗證一下:

>>> hello.__name__
'wrapped'

對于這個小瑕疵,后文將會給出解決方法。

現(xiàn)在,我們梳理一下上面的例子,為了增強原函數(shù) hello 的功能,我們定義了一個函數(shù),它接收原函數(shù)作為參數(shù),并返回一個新的函數(shù),完整的代碼如下:

def makeitalic(func):
    def wrapped():
        return "<i>" + func() + "</i>"
    return wrapped

def hello():
    return 'hello world'

hello = makeitalic(hello)

事實上,makeitalic 就是一個裝飾器(decorator),它『裝飾』了函數(shù) hello,并返回一個函數(shù),將其賦給 hello。

一般情況下,我們使用裝飾器提供的 @ 語法糖(Syntactic Sugar),來簡化上面的寫法:

def makeitalic(func):
    def wrapped():
        return "<i>" + func() + "</i>"
    return wrapped

@makeitalic
def hello():
    return 'hello world'

像上面的情況,可以動態(tài)修改函數(shù)(或類)功能的函數(shù)就是裝飾器。本質上,它是一個高階函數(shù),以被裝飾的函數(shù)(比如上面的 hello)為參數(shù),并返回一個包裝后的函數(shù)(比如上面的 wrapped)給被裝飾函數(shù)(hello)

裝飾器的使用形式

  • 裝飾器的一般使用形式如下:
@decorator
def func():
    pass

等價于下面的形式:

def func():
    pass
func = decorator(func)
  • 裝飾器可以定義多個,離函數(shù)定義最近的裝飾器先被調用,比如:
@decorator_one
@decorator_two
def func():
    pass

等價于:

def func():
    pass

func = decorator_one(decorator_two(func))
  • 裝飾器還可以帶參數(shù),比如:
@decorator(arg1, arg2)
def func():
    pass

等價于:

def func():
    pass

func = decorator(arg1, arg2)(func)

下面我們再看一些具體的例子,以加深對它的理解。

對帶參數(shù)的函數(shù)進行裝飾

前面的例子中,被裝飾的函數(shù) hello() 是沒有帶參數(shù)的,我們看看被裝飾函數(shù)帶參數(shù)的情況。對前面例子中的 hello() 函數(shù)進行改寫,使其帶參數(shù),如下:

def makeitalic(func):
    def wrapped(*args, **kwargs):
        ret = func(*args, **kwargs)
        return '<i>' + ret + '</i>'
    return wrapped

@makeitalic
def hello(name):
    return 'hello %s' % name

@makeitalic
def hello2(name1, name2):
    return 'hello %s, %s' % (name1, name2)

由于函數(shù) hello 帶參數(shù),因此內嵌包裝函數(shù) wrapped 也做了一點改變:

  • 內嵌包裝函數(shù)的參數(shù)傳給了 func,即被裝飾函數(shù),也就是說內嵌包裝函數(shù)的參數(shù)跟被裝飾函數(shù)的參數(shù)對應,這里使用了 (*args, **kwargs),是為了適應可變參數(shù)。

看看使用:

>>> hello('python')
'<i>hello python</i>'
>>> hello2('python', 'java')
'<i>hello python, java</i>'

帶參數(shù)的裝飾器

上面的例子,我們增強了函數(shù) hello 的功能,給它的返回加上了標簽 <i>...</i>,現(xiàn)在,我們想改用標簽 <b>...</b><p>...</p>。是不是要像前面一樣,再定義一個類似 makeitalic 的裝飾器呢?其實,我們可以定義一個函數(shù),將標簽作為參數(shù),返回一個裝飾器,比如:

def wrap_in_tag(tag):
    def decorator(func):
        def wrapped(*args, **kwargs):
            ret = func(*args, **kwargs)
            return '<' + tag + '>' + ret + '</' + tag + '>'
        return wrapped

    return decorator

現(xiàn)在,我們可以根據需要生成想要的裝飾器了:

makebold = wrap_in_tag('b')  # 根據 'b' 返回 makebold 生成器

@makebold
def hello(name):
    return 'hello %s' % name

>>> hello('world')
'<b>hello world</b>'

上面的形式也可以寫得更加簡潔:

@wrap_in_tag('b')
def hello(name):
    return 'hello %s' % name

這就是帶參數(shù)的裝飾器,其實就是在裝飾器外面多了一層包裝,根據不同的參數(shù)返回不同的裝飾器。

多個裝飾器

現(xiàn)在,讓我們來看看多個裝飾器的例子,為了簡單起見,下面的例子就不使用帶參數(shù)的裝飾器。

def makebold(func):
    def wrapped():
        return '<b>' + func() + '</b>'

    return wrapped

def makeitalic(func):
    def wrapped():
        return '<i>' + func() + '</i>'

    return wrapped

@makebold
@makeitalic
def hello():
    return 'hello world'

上面定義了兩個裝飾器,對 hello 進行裝飾,上面的最后幾行代碼相當于:

def hello():
    return 'hello world'

hello = makebold(makeitalic(hello))

調用函數(shù) hello

>>> hello()
'<b><i>hello world</i></b>'

基于類的裝飾器

前面的裝飾器都是一個函數(shù),其實也可以基于類定義裝飾器,看下面的例子:

class Bold(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        return '<b>' + self.func(*args, **kwargs) + '</b>'

@Bold
def hello(name):
    return 'hello %s' % name

>>> hello('world')
'<b>hello world</b>'

可以看到,類 Bold 有兩個方法:

  • __init__():它接收一個函數(shù)作為參數(shù),也就是被裝飾的函數(shù)
  • __call__():讓類對象可調用,就像函數(shù)調用一樣,在調用被裝飾函數(shù)時被調用

還可以讓類裝飾器帶參數(shù):

class Tag(object):
    def __init__(self, tag):
        self.tag = tag

    def __call__(self, func):
        def wrapped(*args, **kwargs):
            return "<{tag}>{res}</{tag}>".format(
                res=func(*args, **kwargs), tag=self.tag
            )
        return wrapped

@Tag('b')
def hello(name):
    return 'hello %s' % name

需要注意的是,如果類裝飾器有參數(shù),則 __init__ 接收參數(shù),而 __call__ 接收 func

裝飾器的副作用

前面提到,使用裝飾器有一個瑕疵,就是被裝飾的函數(shù),它的函數(shù)名稱已經不是原來的名稱了,回到最開始的例子:

def makeitalic(func):
    def wrapped():
        return "<i>" + func() + "</i>"
    return wrapped

@makeitalic
def hello():
    return 'hello world'

函數(shù) hellomakeitalic 裝飾后,它的函數(shù)名稱已經改變了:

>>> hello.__name__
'wrapped'

為了消除這樣的副作用,Python 中的 functools 包提供了一個 wraps 的裝飾器:

from functools import wraps

def makeitalic(func):
    @wraps(func)       # 加上 wraps 裝飾器
    def wrapped():
        return "<i>" + func() + "</i>"
    return wrapped

@makeitalic
def hello():
    return 'hello world'

>>> hello.__name__
'hello'

小結

  • 本質上,裝飾器就是一個返回函數(shù)的高階函數(shù)。
  • 裝飾器可以動態(tài)地修改一個類或函數(shù)的功能,通過在原有的類或者函數(shù)上包裹一層修飾類或修飾函數(shù)實現(xiàn)。
  • 事實上,裝飾器就是閉包的一種應用,但它比較特別,接收被裝飾函數(shù)為參數(shù),并返回一個函數(shù),賦給被裝飾函數(shù),閉包則沒這種限制。

參考資料