virusdefender's blog ʕ•ᴥ•ʔ

使用类来写 Python 的 decorator

平时看到的 Python 的 decorator 都是使用函数来写的,比如说我之前在写的 login_required

 1def login_required(func):
 2    @wraps(func)
 3    def check(*args, **kwargs):
 4        # 在class based views 里面,args 有两个元素,一个是self, 第二个才是request,
 5        # 在function based views 里面,args 只有request 一个参数
 6        request = args[0]
 7        if request.user.is_authenticated():
 8            return func(*args, **kwargs)
 9        if request.is_ajax():
10            return error_response(u"请先登录")
11        else:
12            return HttpResponseRedirect("/login/")
13    return check
14
15@login_required
16def view(request):
17    pass

这样的话,在调用 view 函数的时候就相当于 login_required(view) 了。

其实我们使用类也能完成这样的任务,调用函数就相当于 DecoratorClass(func)()了,后面的括号和调用函数的意思差不多,是在__call__方法中定义的,所以我们就可以这样写

 1class login_required(object):
 2    def __init__(self, func):
 3        self.func = func
 4
 5    def __call__(self, *args, **kwargs):
 6        print args, kwargs
 7        return self.func(*args, **kwargs)
 8
 9@login_required
10def index(*args, **kwargs):
11    print "index page"
12
13index(1, 2, 3, a=4, b=5)

输出就是

1__call__ (1, 2, 3) {'a': 4, 'b': 5}
2index page

看起来一切正常。

至于为什么使用类呢。今天下午在写代码的时候,发现有三个 decorator,结构基本一致,就是判断条件有差别,分别的login_requiredadmin_requiredsuper_admin_required。这不能忍,我就想到了使用类来写,因为可以继承啊,很快就写好了。

 1class BasePermissionDecorator(object):
 2    def __init__(self, func):
 3        self.func = func
 4
 5    def __get__(self, obj, obj_type):
 6        return functools.partial(self.__call__, obj)
 7
 8    def __call__(self, *args, **kwargs):
 9        if len(args) == 2:
10            self.request = args[1]
11        else:
12            self.request = args[0]
13
14        if self.check_permission():
15            return self.func(*args, **kwargs)
16        else:
17            if self.request.is_ajax():
18                return error_response(u"请先登录")
19            else:
20                return HttpResponseRedirect("/login/")
21
22    def check_permission(self):
23        raise NotImplementedError()
24
25
26class login_required(BasePermissionDecorator):
27    def check_permission(self):
28        return self.request.user.is_authenticated()

就只贴 login_required 这一个吧,剩下的基本一样。

然后在运行之前的测试用例的时候出错了,这个 decorator 用在类方法上的时候就会报错,self 参数没了,而普通 def 的函数没问题,demo 如下。

1class IndexView(object):
2    @login_required
3    def post(self, request):
4        print self, request
5
6IndexView()post(1, 2, 3, a=4, b=5)

输出如下

1__call__ (1, 2, 3) {'a': 4, 'b': 5}
21 (2, 3) {'a': 4, 'b': 5}

发现 self 参数没了,匹配到了参数1。

而正常的调用应该是

1<__main__.IndexView object at 0x1013dd890> (1, 2, 3) {'a': 4, 'b': 5}

后来查了一下,说是在 decorator 中需要定义一个__get__方法,

 1class BasePermissionDecorator(object):
 2    def __init__(self, func):
 3        self.func = func
 4
 5    def __get__(self, obj, obj_type):
 6        return functools.partial(self.__call__, obj)
 7
 8    def __call__(self, *args, **kwargs):
 9        if len(args) == 2:
10            self.request = args[1]
11        else:
12            self.request = args[0]
13
14        if self.check_permission():
15            return self.func(*args, **kwargs)
16        else:
17            if self.request.is_ajax():
18                return error_response(u"请先登录")
19            else:
20                return HttpResponseRedirect("/login/")

原因如下。

1class IndexView(object):
2    @login_required
3    def post(self, request):
4        print self, request

相当于

1class IndexView(object):
2    def post(self, request):
3        print self, request
4    post = login_required(post)

所以

1view = IndexView() 
2view.post

就相当于view.post.__get__(post, view, <type of view>)就相当于login_required(post).__get__(view, <type of view>),也就是调用的class login_required里面的__get__方法,因为我们使用了partial函数,相当于返回了一个指定了一个参数为 view 的函数,然后再调用的时候再增加一个其他参数。

至于为什么会调用__get__函数,可以参考http://intermediatepythonista.com/classes-and-objects-ii-descriptors,这是 Python 的 descriptor。

提交评论 | 微信打赏 | 转载必须注明原文链接

#Python