1. 程式人生 > >python -- 裝飾器的高階應用

python -- 裝飾器的高階應用

裝飾器和裝飾器模式
裝飾器模式是面向物件的一種設計模式,支援將行為動態增加到已經存在的物件上。當裝飾一個物件的時候,就表示獨立與其他類例項物件,為該物件擴充套件了新的功能。

python的裝飾器不是裝飾器模式的一種實現。python裝飾器是在定義的時候對函式或方法增加功能,而不是在執行的時候增加。
裝飾器模式可以在python中實現,但是這樣做意義不大。因為python是鴨子型別風格的程式語言。鴨子型別(英語:duck typing)是動態型別的一種風格。

一個基礎的裝飾器

import time
import datetime


def time_this(original_func):
    def new_func(*args, **kwargs):
        start_a = datetime.datetime.now()
        x = original_func(*args, **kwargs)
        end_a = datetime.datetime.now()
        print("Elapsed Time = {0}".format(start_a - end_a))
        return x
    return new_func

@time_this
def func_a(stuff):
    print("i need a sleep.")
    time.sleep(3)

func_a(1)

執行結果:

i need a sleep.
Elapsed Time = -1 day, 23:59:56.999700

帶有引數的裝飾器
有時候,除了完成其裝飾的函式外,還可以帶上引數。這種技術常用於註冊類似功能。比如:

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

假設有個應用,使用者可以通過gui登入。使用者和gui觸發時間互動呼叫python函式。不同的使用者有不同的許可權。執行不同的函式需要不同了型別的許可權。比如:

#assume these functions exist
def current_user_id():
    """
    this function returns the current logged in user id, if the user is not authenticated then return None 
    """
    
def get_permissions(iUserId):
    """
    returns a list of permission strings for the given user. For example ['logged_in','administrator','premium_member']
    """

#we need to implment permission checking on these functions
    
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(func):
    def ret_func(*args,**kwargs):
        permissions = get_permissions(current_user_id())
        if 'administrator' in permissions:
            return func(*args,**kwargs)
        else:
            raise Exception("Not allowed")
    return ret_func

def requires_logged_in(func):
    def ret_func(*args,**kwargs):
        permissions = get_permissions(current_user_id())
        if 'logged_in' in permissions:
            return func(*args,**kwargs)
        else:
            raise Exception("Not allowed")
    return ret_func
    
def requires_premium_member(func):
    def ret_func(*args,**kwargs):
        permissions = get_permissions(current_user_id())
        if 'premium_member' in permissions:
            return func(*args,**kwargs)
        else:
            raise Exception("Not allowed")
    return ret_func
    
@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(func):                                            
        def decorated(*args,**kwargs):                            
            permissions = get_permissions(current_user_id())     
            if sPermission in permissions:                       
                return func(*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
   """

通用的裝飾器程式碼示例:

def outer_decorator(*outer_args,**outer_kwargs):                            
    def decorator(func):                                            
        def decorated(*args,**kwargs):                            
            do_something(*outer_args,**outer_kwargs)                      
            return func(*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(func):                                            
    def decorated(*args,**kwargs):                            
        do_something(1,2,3)                      
        return func(*args,**kwargs)                         
    return decorated                                          
return decorator       
    
@decorator
def foo(a,b,c):
    print a
    print b
    print c


foo()

裝飾類
裝飾器並不僅僅限於裝飾函式,也可以裝飾類。
假如我們有個類,需要完成很多重要工作,我們想計時這個類完成每項工作需要的時間。我們可以使用上面定義好的time_this:

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

上面這樣做是可以實現,但是要新增很多額外的程式碼行在類定義中。
如果我們寫了很多類的方法,忘記了其中對其中個別函式進行裝飾怎麼辦?或者如果我們不再需要計時功能呢。
可使用以下的方式進行優化:

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

上面的程式碼等價於:

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

那麼time_all_class_methods是如何工作的呢?
首先、它需要一個類作為引數,並返回一個類。返回的類的功能看起來應該和原先的importstuff類類似。這裡我們可以這麼做:

import datetime
import time

def time_this(original_func):      
    print ("decorating")                  
    def new_func(*args,**kwargs):
        print("starting timer")             
        start = datetime.datetime.now()                     
        x = original_func(*args,**kwargs)                
        end	= datetime.datetime.now()                      
        print "Elapsed Time = {0}".format(end-start)      
        return x                                             
    return new_func

def time_all_class_methods(Cls):
    class NewCls(object):
        def __init__(self,*args,**kwargs):
            self.oInstance = Cls(*args,**kwargs)
        def __getattribute__(self,s):
            """
            this is called whenever any attribute of a NewCls object is accessed. This function first tries to 
            get the attribute off NewCls. If it fails then it tries to fetch the attribute from self.oInstance (an
            instance of the decorated class). If it manages to fetch the attribute from self.oInstance, and 
            the attribute is an instance method then `time_this` is applied.
            """
            try:    
                x = super(NewCls,self).__getattribute__(s)
            except AttributeError:      
                pass
            else:
                return x
            x = self.oInstance.__getattribute__(s)
            if type(x) == type(self.__init__): # it is an instance method
                return time_this(x)                 # this is equivalent of just decorating the method with time_this
            else:
                return x
    return NewCls

#now lets make a dummy class to test it out on:

@time_all_class_methods
class Foo(object):
    def a(self):
        print "entering a"
        import time
        time.sleep(3)
        print "exiting a"

oF = Foo()
oF.a()