Python 裝飾器語法詳解

Python 裝飾器(decorator)是一種語法糖(Syntactic sugar),

@d
def foo():
    pass

以上代碼和以下代碼是等價的:

foo = d(foo)

進一步說,

@d(param)
def foo():
    pass

和下面代碼是等價的

foo = (d(param))(foo)

他們的實質都是傳入一個函數(shù)對象,進行“裝飾”后返回裝飾好的另一個函數(shù)對象。

返回函數(shù)對象的函數(shù)

Python 中的函數(shù)是一個對象(object),它可以像任何其他對象一樣被創(chuàng)建、引用、傳遞和銷毀。引用函數(shù)對象的方法是使用它的函數(shù)名,如 foo 引用的是一個函數(shù)對象,而 foo() 則是其返回值了。

得益于函數(shù)在 Python 中一等公民的地位,你可以在一個函數(shù)中返回另一個函數(shù),接受函數(shù)作為參數(shù)或返回函數(shù)的函數(shù),稱為高階函數(shù)(higher-order function)。

看下面的例子:

def f(a):
    def g(b):
        def h(c):
            print(a, b, c)
        return h
    return g

In [2]: f(1)(2)(3)
1 2 3

In [3]: g = f(4)

In [4]: g(5)(6)
4 5 6

這是在函數(shù)式編程中被稱為柯里化(currying)的東西,被柯里化后的高階函數(shù) f 其實是這樣執(zhí)行的:

((f(1))(2))(3)

首先執(zhí)行的 f(1),返回其內(nèi)部定義的函數(shù)對象 g,接著執(zhí)行 g(2),返回了 g 內(nèi)部定義的對象 h,以此類推。

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

最簡單的情形就是不帶參數(shù)的裝飾器函數(shù)。
給函數(shù) foo 使用裝飾器 d,等價于

foo = d(foo)

為了定義裝飾器 d,必須使其接受一個函數(shù)對象參數(shù),并返回一個函數(shù)對象,而要返回一個函數(shù)對象,就需要在 d 內(nèi)部新定義一個函數(shù)對象。這個內(nèi)部的函數(shù)對象有什么特點呢?它接受 foo 的參數(shù),運行 foo 的代碼,并返回 foo 的返回值,當然還做了裝飾器需要做的額外工作。

按照以上需求,裝飾器 d 的代碼就可以寫出了。

def d(f):
    def wrapper(*args, **kwargs):
        # do something
        res = f(*args, **kwargs)
        # do something
        return res
    
    return wrapper

d(foo) 返回了函數(shù)對象 wrapper,而變量 foo 指向了它。foo 不再是原來的那個 foo,裝飾器賦予了它額外的功能。

如果需要的話,d 其實是可以這樣被調(diào)用的:

In [35]: def foo(n):
    ...:     return n + 1
    ...:

In [36]: d(foo)(2)
Out[36]: 3

是不是就是上面柯里化的形式?

另一種裝飾器就更容易理解了:

def d(f):
    f.__some_attr__ = 'attr'
    return f

根本就沒有重新定義一個函數(shù)對象,改了 f 的某個屬性就把它扔出來了。

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

帶參數(shù)的裝飾器函數(shù)這樣使用:

@d(param)
def foo(n):
    return n + 1

它等價于

foo = d(param)(foo)

為此,首先需要定義一個接受裝飾器參數(shù) param 的函數(shù),接著再定義接受 foo 為參數(shù)的函數(shù),最后是接受函數(shù) foo 本身參數(shù)為參數(shù)的函數(shù),除了最內(nèi)層函數(shù)返回 foo() 運行結果外,其他函數(shù)都返回一個函數(shù)對象。

def d(param):
    def outter(f):
        def inner(*args, **kwargs):
            # do something
            res = f(*args, **kwargs)
            # do something
            return res
        return inner
    return outter

你當然也可以這樣調(diào)用 d:

d(param)(foo)(3)

callable

如果一個類的實例 instance 具有 __call__ 方法,那么這個實例就是一個 callable,instance() 就相當于 instance.__call__()。

不帶參數(shù)的裝飾器類

仍然沿用以上對裝飾器語法糖的解釋,對函數(shù) foo 使用裝飾器類 D,等價于

foo = D(foo)

foo 對象應該在 D.__init__() 中被傳入,得到一個 callable,而 foo 在 D.__call__() 中被執(zhí)行。所以一個不帶參數(shù)的裝飾器類應該寫成這樣:

class D(object):
    def __init__(self, f):
        self.f = f
        
    def __call__(self, *args, **kwargs):
        # do something
        res = self.f(*args, **kwargs)
        # do something
        return res

帶參數(shù)的裝飾器類

使用帶參數(shù)的裝飾器類,等價于

foo = D(params)(foo)

可以知道,裝飾器的參數(shù) params 在 D.__init__() 中被傳入,而 D.__call__() 接受函數(shù)對象 foo, 返回裝飾后的函數(shù)對象。因而裝飾器類是這樣的:

class D(object):
    def __init__(self, param):
        self.param = param
        
    def __call__(self, f):
        def wrapper(*args, **kwargs):
            # do something
            res = f(*args, **kwargs)
            # do something
            return res
        
        return wrapper

總結

本文提供了將裝飾器轉化成高階函數(shù)表達的一種范式,根據(jù)這一范式,可以直接推導出四種裝飾器(帶參/不帶參、函數(shù)/類)的寫法。

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

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

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評論 19 139
  • 前言 人生苦多,快來 Kotlin ,快速學習Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,669評論 9 118
  • 本文為《爬著學Python》系列第四篇文章。從本篇開始,本專欄在順序更新的基礎上,會有不規(guī)則的更新。 在Pytho...
    SyPy閱讀 2,570評論 4 11
  • 這天中午十二點半你發(fā)來微信問我:吃飯了嗎?我沒有看見。十二點四十,你又問我:在干什么,為什么不回消息。等看見的時候...
    張揚趁年華閱讀 216評論 2 2
  • 1、拉流: clone git clone git@github.com:arnozhang/Android-Sl...
    牧秦丶閱讀 265評論 0 0

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