假设我们使用flask进行开发,我们需要检查当前用户是否有某个操作权限。fromflaskimportabortfromflask_loginimportcurrent_userfrom.modelsimportPermission让我们从最简单的情况开始。f是一些动作函数,比如添加评论。那么想要添加评论的用户需要有评论权限Permission.COMMENT。最直接的检查操作权限的逻辑当然是使用条件判断语句:ifnotcurrent_user.can(Permission.COMMENT):abort(403)f(*args,**kwargs)为了不写if每次之前调用f语句,我们将这一层逻辑包含到一个新的函数中,则有:defdecorated_function(*args,**kwargs):ifnotcurrent_user.can(Permission.COMMENT):abort(403)returnf(*args,**kwargs)除非当前用户没有操作权限,否则decorated_function会做和f完全一样的事情。我们想要的是这样一个函数。然而,目前decorated_function只能做与f相同的事情。如果你想要另一个函数g的相同函数,你是否必须编写另一个defdecorated_function_new(*args,**kwargs):ifnotcurrent_user.can(Permission.COMMENT):abort(403)returng(*args,**kwargs)?decorated_function_new和decorated_function之间的唯一区别是返回值是f(*args,**kwargs)还是g(*args,**kwargs)。那么我们可以再定义一个函数,将f或者g作为参数传入,就可以解决问题了。defdecorator(f):defdecorated_function(*args,**kwargs):ifnotcurrent_user.can(Permission.COMMENT):abort(403)returnf(*args,**kwargs)returndecorated_function这样,对于任何functionf,我们都可以调用decorator(f)得到一个对应的函数,包括执行操作前检查评论权限。请注意,这只是一个函数,需要调用(并传递参数)以获取返回值。decorator(f)(*args,**kwargs)这里的函数装饰器是一个装饰器,可以在代码执行过程中动态增加f的函数,而不用改变f的定义。使用@将此装饰器放在函数定义之前以替换原始函数。@decoratordefadd_comment():'''添加评论'''...这等同于:add_comment=decorator(add_comment)但是,由于装饰器正在替换原始函数,我们失去了原始函数的一些其他特征。例如:print(add_comment.__name__)print(add_comment.__doc__)将输出decorated_function和None而不是add_comment并添加注释。如果多个函数被装饰器装饰,它们的__name__和__doc__属性将被掩盖,一些依赖于函数签名的代码执行会出错。为了避免这样的事情,可以使用functools库中的wrap来复制f的相关属性。从functoolsimportwraps...defdecorator(f):@wraps(f)defdecorated_function(*args,**kwargs):如果不是current_user.can(Permission.COMMENT):abort(403)returnf(*args,**kwargs)returndecorated_function现在我们已经实现了一个装饰器,我们只能用它来检查评论权限。我们可以定义一个装饰器工厂函数来生成检查各种权限的装饰器。defpermission_required(permission):defdecorator(f):@wraps(f)defdecorated_function(*args,**kwargs):如果不是current_user.can(permission):abort(403)returnf(*args,**kwargs)returndecorated_functionreturndecorator...@permission_required(Permission.WRITE)defnew_post(user):...在这个用三层def包裹的洋葱中,最里面的def添加了除原始函数之外的逻辑,第一层第二层def的f作为参数,这样装饰器就可以用在任何其他函数上,第三层用来传入参数来控制装饰器的行为。如果我们想用permission_required生成一个单独的装饰器来检查管理员权限:defadmin_required(f):returnpermission_required(Permission.ADMIN)(f)也应该写成admin_required=permission_required(Permission.ADMIN),但是仍然使用定义函数形式更好。
