装饰器和装饰器模式
装饰器模式是面向对象的一种设计模式,支持将行为动态增加到已经存在的对象上。当装饰一个对象的时候,就表示独立与其他类实例对象,为该对象扩展了新的功能。
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()