Python裝飾器的高級用法

Python裝飾器的高級用法(翻譯)

原文地址
https://www.codementor.io/python/tutorial/advanced-use-python-decorators-class-function

介紹

我寫這篇文章的主要目的是介紹裝飾器的高級用法宣决。如果你對裝飾器知之甚少蚁孔,或者對本文講到的知識點易混淆。我建議你復習下裝飾器基礎教程贞盯。
本教程的目標是介紹裝飾器的一些有趣的用法镀赌。特別是怎樣在類中使用裝飾器氯哮,怎樣給裝飾器傳遞額外的參數。

裝飾器 vs 裝飾器模式

Decorator模式是一個面向對象的設計模式商佛,它允許動態(tài)地往現有的對象添加行為喉钢。當你裝飾了一個對象,在某種程度上良姆,你是在獨立于同一個類的其他實例的基礎上擴展其功能肠虽。
Python裝飾器不是裝飾器模式的實現,它在函數玛追、方法定義的時候添加功能税课,而不是在運行的時候添加。Decorator設計模式本身可以在Python中實現豹缀,因為Python是動態(tài)編程語言伯复,所以沒有必要這樣做慨代。

一個基礎的裝飾器

這是裝飾器的最簡單例子邢笙,在繼續(xù)往下面閱讀之前請確保理解此段代碼。如果你需要更多關于此代碼的解釋侍匙,請復習下基礎裝飾器教程氮惯。

def time_this(original_function): 
    def new_function(*args, **kwargs):
        import datetime 
        before = datetime.datetime.now() 
        x = original_function(*args, **kwargs) 
        after = datetime.datetime.now() 
        print("Elapsed Time = {}".format(after-before)) 
        return x 
    return new_function
@time_this
def func_a(stuff): 
    import time 
    time.sleep(stuff) 
    func_a(3)
# out:
Elapsed Time = 0:00:03.012472

帶參數的裝飾器

有時候帶參數的裝飾器會非常有用,這種技術經常用在函數注冊中想暗。在web框架Pyramid中經常有用到妇汗,例如:

@view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request): 
    return {'project': 'hello decorators'} 

比方說,我們有一個用戶可以登錄并且可以和用戶交互的GUI應用程序说莫。用戶和GUI界面的交互觸發(fā)事件杨箭,導致Python函數執(zhí)行。假設有許多使用該圖形界面的用戶储狭,他們各自的權限級別差異很大互婿,不同的功能執(zhí)行需要不同的權限捣郊。比如,考慮以下功能:

# 假設這些函數是存在的
def current_user_id(): 
    """ this function returns the current logged in user id, if the use is not authenticated the return None """
def get_permissions(iUserId): 
    """ returns a list of permission strings for the given user. For example ['logged_in','administrator','premium_member'] """
# 在這些函數中我們需要實現權限檢查 
def delete_user(iUserId): 
    """ delete the user with the given Id. This function is only accessable to users with administrator permissions """ 
def new_game(): 
    """ any logged in user can start a new game """ 
def premium_checkpoint(): 
    """ save the game progress, only accessable to premium members """

一種實現這些權限檢查的方式是實現多個裝飾器慈参,比如:

def requires_admin(fn):  
    def ret_fn(*args,**kwargs): 
        lPermissions = get_permissions(current_user_id()) 
        if 'administrator' in lPermissions: 
            return fn(*args,**kwargs) 
        else: raise Exception("Not allowed") 
    return ret_fn
def requires_logged_in(fn): 
    def ret_fn(*args,**kwargs): 
        lPermissions = get_permissions(current_user_id()) 
        if 'logged_in' in lPermissions: 
            return fn(*args,**kwargs) 
        else: 
            raise Exception("Not allowed") 
        return ret_fn 
def requires_premium_member(fn): 
    def ret_fn(*args,**kwargs): 
        lPermissions = get_permissions(current_user_id()) 
        if 'premium_member' in lPermissions: 
            return fn(*args,**kwargs) 
        else: 
            raise Exception("Not allowed") 
        return ret_fn 
@requires_admin
def delete_user(iUserId): 
""" delete the user with the given Id. This function is only accessable to users with administrator permissions """
@requires_logged_in
def new_game(): 
""" any logged in user can start a new game """ @requires_premium_member
def premium_checkpoint(): 
""" save the game progress, only accessable to premium members """

但是呛牲,這太可怕了。這需要大量的復制粘貼驮配,每個裝飾器需要一個不同的名字娘扩,如果有任何關于權限檢查的改變,每個裝飾器都需要修改壮锻。就沒有一個裝飾器把以上三個裝飾器的工作都干了的嗎琐旁?
為了解決此問題,我們需要一個返回裝飾器的函數:

def requires_permission(sPermission):  
    def decorator(fn):  
        def decorated(*args,**kwargs):  
            lPermissions = get_permissions(current_user_id())  
            if sPermission in lPermissions:  
                return fn(*args,**kwargs)
            raise Exception("permission denied")  
        return decorated  
    return decorator
def get_permissions(iUserId):  
    # this is here so that the decorator doesn't throw NameErrors 
    return ['logged_in',]
def current_user_id(): 
    #ditto on the NameErrors 
    return 1
#and now we can decorate stuff... 
@requires_permission('administrator')
def delete_user(iUserId): 
""" delete the user with the given Id. This function is only accessible to users with administrator permissions """
@requires_permission('logged_in')
def new_game(): 
""" any logged in user can start a new game """ @requires_permission('premium_member')
def premium_checkpoint(): 
""" save the game progress, only accessable to premium members """

嘗試一下調用delete_user猜绣,new namepremium_checkpoint然后看看發(fā)生了什么旋膳。
premium_checkpointdelete_user 產生了一個“permission denied”的異常,new_game執(zhí)行正常途事。
下面是帶參數裝飾的一般形式验懊,和例子的使用:

def outer_decorator(*outer_args,**outer_kwargs):  
    def decorator(fn):  
        def decorated(*args,**kwargs):
            do_something(*outer_args,**outer_kwargs) 
            return fn(*args,**kwargs)  
        return decorated  
    return decorator  
@outer_decorator(1,2,3)
def foo(a,b,c): 
    print(a) 
    print(b) 
    print(c)
foo()

等價于:

def decorator(fn):  
    def decorated(*args,**kwargs):  
        do_something(1,2,3)  
        return fn(*args,**kwargs)  
    return decorated 
return decorator  
@decorator
def foo(a,b,c): 
    print(a)
    print(b) 
    print(c)
foo()

類裝飾器

裝飾器不僅可以修飾函數,還可以對類進行裝飾尸变。比如說义图,我們有一個類,該類含有許多重要的方法召烂,我們需要記錄每一個方法執(zhí)行的時間碱工。我們可以使用上述的time_this裝飾此類:

class ImportantStuff(object): 
@time_this 
def do_stuff_1(self): 
    pass
@time_this 
def do_stuff_2(self): 
    pass
@time_this 
def do_stuff_3(self): 
    pass

此方法可以運行正常。但是在該類中存在許多多余的代碼奏夫,如果我們想建立更多的類方法并且遺忘了裝飾其中的一個方法怕篷,如果我們不想裝飾該類中的方法了,會發(fā)生什么樣的情況呢酗昼?這可能會存在出現認為錯誤的空間廊谓,如果寫成這樣會更有好:

@time_all_class_methods
class ImportantStuff: 
    def do_stuff_1(self):
        pass
    def do_stuff_2(self):
        pass
    def do_stuff_3(self):
        pass

等價于:

class ImportantStuff: 
    def do_stuff_1(self):
        pass
    def do_stuff_2(self):
        pass
    def do_stuff_3(self):
        pass
ImportantStuff = time_all_class_methods(ImportantStuff)

那么time_all_class_methods是怎么工作的呢?
首先麻削,我們需要采用一個類作為參數蒸痹,然后返回一個類,我們也要知道返回的類的功能應該和原始類ImportantStuff功能一樣呛哟。也就是說叠荠,我們仍然希望做重要的事情,我們希望記錄下每個步驟發(fā)生的時間扫责。我們寫成這樣:

def time_this(original_function):  
    print("decorating")  
    def new_function(*args,**kwargs): 
        print("starting timer")  
        import datetime  
        before = datetime.datetime.now()  
        x = original_function(*args,**kwargs)  
        after = datetime.datetime.now()  
        print("Elapsed Time = {0}".format(after-before))  
        return x  
    return new_function
def time_all_class_methods(Cls): 
    class NewCls: 
        def __init__(self,*args,**kwargs): 
            self.oInstance = Cls(*args,**kwargs) 
        def __getattribute__(self,s): 
            try:  
                x = super(NewCls,self).__getattribute__(s) 
            except AttributeError:  
                pass 
            else: 
                return x 
            x = self.oInstance.__getattribute__(s) 
            if type(x) == type(self.__init__): 
                return time_this(x) 
            else: 
                return x 
        return NewCls
@time_all_class_methods
class Foo: 
    def a(self): 
        print("entering a") 
        import time 
        time.sleep(3) 
        print("exiting a")
oF = Foo()
oF.a()
# out:
decorating
starting timer
entering a
exiting a
Elapsed Time = 0:00:03.006767

總結

在此篇教程中榛鼎,我們給大家展示了一些Python裝飾器使用的技巧-我們介紹了怎么樣把參數傳遞給裝飾器,怎樣裝飾類。但是這僅僅是冰山一角者娱。除了本文介紹的之外蜘渣,還有其他好多裝飾器的使用方法,我們甚至可以使用裝飾器裝飾裝飾器(如果你有機會使用到它肺然,這可能是一個做全面檢查的好方法)蔫缸。Python有一些內置的裝飾器,比如:staticmethod际起,classmethod
閱讀完本文還需要學習什么呢拾碌?通常是沒有比我在文章中展示的裝飾器更復雜的了,如果你有興趣學習更多關于改變類功能的方法街望,我建議您閱讀下繼承和OOP設計原則校翔。或者你可以試試閱讀一下元類灾前。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末防症,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子哎甲,更是在濱河造成了極大的恐慌蔫敲,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炭玫,死亡現場離奇詭異奈嘿,居然都是意外死亡,警方通過查閱死者的電腦和手機吞加,發(fā)現死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門裙犹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衔憨,你說我怎么就攤上這事叶圃。” “怎么了践图?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵掺冠,是天一觀的道長。 經常有香客問我平项,道長赫舒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任闽瓢,我火速辦了婚禮,結果婚禮上心赶,老公的妹妹穿的比我還像新娘扣讼。我一直安慰自己,他們只是感情好缨叫,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布椭符。 她就那樣靜靜地躺著荔燎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪销钝。 梳的紋絲不亂的頭發(fā)上有咨,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音蒸健,去河邊找鬼座享。 笑死,一個胖子當著我的面吹牛似忧,可吹牛的內容都是我干的渣叛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盯捌,長吁一口氣:“原來是場噩夢啊……” “哼淳衙!你這毒婦竟也來了?” 一聲冷哼從身側響起饺著,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤箫攀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后幼衰,有當地人在樹林里發(fā)現了一具尸體匠童,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年塑顺,在試婚紗的時候發(fā)現自己被綠了汤求。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡严拒,死狀恐怖扬绪,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情裤唠,我是刑警寧澤挤牛,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站种蘸,受9級特大地震影響墓赴,放射性物質發(fā)生泄漏。R本人自食惡果不足惜航瞭,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一诫硕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刊侯,春花似錦章办、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挪蹭。三九已至,卻和暖如春休偶,著一層夾襖步出監(jiān)牢的瞬間梁厉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工踏兜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留词顾,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓庇麦,卻偏偏與公主長得像计技,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子山橄,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理垮媒,服務發(fā)現,斷路器航棱,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 每個人都有的內褲主要功能是用來遮羞睡雇,但是到了冬天它沒法為我們防風御寒,咋辦饮醇?我們想到的一個辦法就是把內褲改造一下它抱,...
    chen_000閱讀 1,364評論 0 3
  • www.yunxcloud.cn 首先要明白裝飾器是用來給函數增加額外功能的。 常用的工具函數 import ti...
    彩色系閱讀 1,016評論 0 1
  • 兩本不錯的書: 《Python參考手冊》:對Python各個標準模塊朴艰,特性介紹的比較詳細观蓄。 《Python核心編程...
    靜熙老師哈哈哈閱讀 3,360評論 0 80
  • 她放學回家,打開手機 聊天記錄仍然一片空白 看一部回憶的電影—— 在那里祠墅,有他作主角侮穿; 想像依據回憶在虛無中 莽撞...
    西夏伏閱讀 135評論 0 0