Python裝飾器的高級用法

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

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

介紹

我寫這篇文章的主要目的是介紹裝飾器的高級用法。如果你對裝飾器知之甚少,或者對本文講到的知識點(diǎn)易混淆。我建議你復(fù)習(xí)下裝飾器基礎(chǔ)教程。
本教程的目標(biāo)是介紹裝飾器的一些有趣的用法。特別是怎樣在類中使用裝飾器,怎樣給裝飾器傳遞額外的參數(shù)。

裝飾器 vs 裝飾器模式

Decorator模式是一個(gè)面向?qū)ο蟮脑O(shè)計(jì)模式,它允許動(dòng)態(tài)地往現(xiàn)有的對象添加行為。當(dāng)你裝飾了一個(gè)對象,在某種程度上,你是在獨(dú)立于同一個(gè)類的其他實(shí)例的基礎(chǔ)上擴(kuò)展其功能。
Python裝飾器不是裝飾器模式的實(shí)現(xiàn),它在函數(shù)、方法定義的時(shí)候添加功能,而不是在運(yùn)行的時(shí)候添加。Decorator設(shè)計(jì)模式本身可以在Python中實(shí)現(xiàn),因?yàn)镻ython是動(dòng)態(tài)編程語言,所以沒有必要這樣做。

一個(gè)基礎(chǔ)的裝飾器

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

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

帶參數(shù)的裝飾器

有時(shí)候帶參數(shù)的裝飾器會(huì)非常有用,這種技術(shù)經(jīng)常用在函數(shù)注冊中。在web框架Pyramid中經(jīng)常有用到,例如:

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

比方說,我們有一個(gè)用戶可以登錄并且可以和用戶交互的GUI應(yīng)用程序。用戶和GUI界面的交互觸發(fā)事件,導(dǎo)致Python函數(shù)執(zhí)行。假設(shè)有許多使用該圖形界面的用戶,他們各自的權(quán)限級別差異很大,不同的功能執(zhí)行需要不同的權(quán)限。比如,考慮以下功能:

# 假設(shè)這些函數(shù)是存在的
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'] """
# 在這些函數(shù)中我們需要實(shí)現(xiàn)權(quán)限檢查 
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 """

一種實(shí)現(xiàn)這些權(quán)限檢查的方式是實(shí)現(xiàn)多個(gè)裝飾器,比如:

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 """

但是,這太可怕了。這需要大量的復(fù)制粘貼,每個(gè)裝飾器需要一個(gè)不同的名字,如果有任何關(guān)于權(quán)限檢查的改變,每個(gè)裝飾器都需要修改。就沒有一個(gè)裝飾器把以上三個(gè)裝飾器的工作都干了的嗎?
為了解決此問題,我們需要一個(gè)返回裝飾器的函數(shù):

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 """

嘗試一下調(diào)用delete_usernew namepremium_checkpoint然后看看發(fā)生了什么。
premium_checkpointdelete_user 產(chǎn)生了一個(gè)“permission denied”的異常,new_game執(zhí)行正常。
下面是帶參數(shù)裝飾的一般形式,和例子的使用:

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

等價(jià)于:

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

類裝飾器

裝飾器不僅可以修飾函數(shù),還可以對類進(jìn)行裝飾。比如說,我們有一個(gè)類,該類含有許多重要的方法,我們需要記錄每一個(gè)方法執(zhí)行的時(shí)間。我們可以使用上述的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

此方法可以運(yùn)行正常。但是在該類中存在許多多余的代碼,如果我們想建立更多的類方法并且遺忘了裝飾其中的一個(gè)方法,如果我們不想裝飾該類中的方法了,會(huì)發(fā)生什么樣的情況呢?這可能會(huì)存在出現(xiàn)認(rèn)為錯(cuò)誤的空間,如果寫成這樣會(huì)更有好:

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

等價(jià)于:

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是怎么工作的呢?
首先,我們需要采用一個(gè)類作為參數(shù),然后返回一個(gè)類,我們也要知道返回的類的功能應(yīng)該和原始類ImportantStuff功能一樣。也就是說,我們?nèi)匀幌M鲋匾氖虑?,我們希望記錄下每個(gè)步驟發(fā)生的時(shí)間。我們寫成這樣:

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

總結(jié)

在此篇教程中,我們給大家展示了一些Python裝飾器使用的技巧-我們介紹了怎么樣把參數(shù)傳遞給裝飾器,怎樣裝飾類。但是這僅僅是冰山一角。除了本文介紹的之外,還有其他好多裝飾器的使用方法,我們甚至可以使用裝飾器裝飾裝飾器(如果你有機(jī)會(huì)使用到它,這可能是一個(gè)做全面檢查的好方法)。Python有一些內(nèi)置的裝飾器,比如:staticmethodclassmethod
閱讀完本文還需要學(xué)習(xí)什么呢?通常是沒有比我在文章中展示的裝飾器更復(fù)雜的了,如果你有興趣學(xué)習(xí)更多關(guān)于改變類功能的方法,我建議您閱讀下繼承和OOP設(shè)計(jì)原則?;蛘吣憧梢栽囋囬喿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)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,616評論 19 139
  • 每個(gè)人都有的內(nèi)褲主要功能是用來遮羞,但是到了冬天它沒法為我們防風(fēng)御寒,咋辦?我們想到的一個(gè)辦法就是把內(nèi)褲改造一下,...
    chen_000閱讀 1,403評論 0 3
  • www.yunxcloud.cn 首先要明白裝飾器是用來給函數(shù)增加額外功能的。 常用的工具函數(shù) import ti...
    彩色系閱讀 1,094評論 0 1
  • 兩本不錯(cuò)的書: 《Python參考手冊》:對Python各個(gè)標(biāo)準(zhǔn)模塊,特性介紹的比較詳細(xì)。 《Python核心編程...
    靜熙老師哈哈哈閱讀 3,443評論 0 80
  • 她放學(xué)回家,打開手機(jī) 聊天記錄仍然一片空白 看一部回憶的電影—— 在那里,有他作主角; 想像依據(jù)回憶在虛無中 莽撞...
    西夏伏閱讀 171評論 0 0

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