Python|Python装饰器以及高级用法( 三 )

接受参数的装饰器
有时 , 除了装饰的函数之外 , 装饰器还可以使用参数 。 这种技术经常用于函数注册等事情 。 一个著名的例子是Pyramid Web应用程序框架中的视图配置 。 例如:
@view_config(route_name='home', renderer='templates/mytemplate.pt')def my_view(request): return {'project': 'hello decorators'}假设我们有一个应用程序 , 用户可以登录并与一个漂亮的gui(图形用户界面)进行交互 。 用户与gui的交互触发事件 , 而这些事件导致Python函数被执行 。 让我们假设有很多用户使用这个应用程序 , 并且他们有许多不同的权限级别 。 执行不同的功能需要不同的权限类型 。 例如 , 考虑以下功能:
#这些功能是存在的def current_user_id(): """ 此函数返回当前登录的用户ID , 如果没有经过身份验证 , 则返回None"""def get_permissions(iUserId): """ 返回给定用户的权限字符串列表 , 例如 ['logged_in','administrator','premium_member'] """#我们需要对这些函数进行权限检查def delete_user(iUserId): """ 删除具有给定ID的用户 , 只有管理员权限才能访问此函数 """def new_game(): """ 任何已登录的用户都可以启动一个新游戏 """def premium_checkpoint(): """ 保存游戏进程 , 只允许高级成员访问 """实现这些权限的一种方法是创建多个装饰器 , 例如:
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_fndef 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_fndef 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_admindef delete_user(iUserId): """ 删除具有给定Id的用户 , 只有具有管理员权限的用户才能访问此函数 """@requires_logged_in def new_game(): """ 任何已登录的用户都可以启动一个新游戏 """@requires_premium_memberdef premium_checkpoint(): """ 保存游戏进程 , 只允许高级成员访问 """但这太可怕了 。 它需要大量的复制粘贴 , 并且每个装饰器需要不同的名称 , 如果对权限的检查方式进行了任何更改 , 则必须更新每个装饰器 。 有一个装饰器可以完成这三个工作不是很好吗?
为此 , 我们需要一个返回装饰器的函数:
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 decoratedreturn decorator def get_permissions(iUserId): #这样装饰器就不会抛出NameError return ['logged_in',]def current_user_id(): #名称错误也是如此 return 1#现在我们可以进行装饰了 @requires_permission('administrator')def delete_user(iUserId): """ 删除具有给定Id的用户 , 只有具有管理员权限的用户才能访问此函数 """@requires_permission('logged_in')def new_game(): """ 任何已登录的用户都可以启动一个新游戏 """@requires_permission('premium_member')def premium_checkpoint(): """ 保存游戏进程 , 只允许高级成员访问 """尝试调用delete_user , new_game和premium_checkpoint看看会发生什么 。
premium_checkpoint和delete_user都在消息“权限被拒绝”的情况下引发异常 , new_game执行得很好(但没有太多的作用) 。
下面是装饰器的一般形式 , 带有参数和使用说明:
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 decoratedreturn 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 @decoratordef 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): ...这样就可以了 。 但是这个类中还有一些额外的代码行 。 如果我们写一些更多的类方法并忘记装饰它们中的一个呢?如果我们决定不再为进行计时怎么办?这里肯定存在人为错误的空间 。 这样编写它会好得多: