给装饰器添加参数

5 投票
6 回答
3226 浏览
提问于 2025-04-15 16:42

我有一个装饰器,用来装饰一个Django视图,当我不想让这个视图执行时,如果share参数为True(这个是通过中间件处理的)

class no_share(object):
    def __init__(self, view):
        self.view = view

    def __call__(self, request, *args, **kwargs):
        """Don't let them in if it's shared"""

        if kwargs.get('shared', True):
            from django.http import Http404
            raise Http404('not availiable for sharing')

        return self.view(request, *args, **kwargs)

现在它的工作方式是这样的:

@no_share
def prefs(request, [...])

但我想稍微扩展一下功能,让它可以这样工作:

@no_share('prefs')
def prefs(request, [...])

我的问题是,我该如何修改这个装饰器类,让它接受额外的参数呢?

6 个回答

1

我觉得闭包在这里可能会起作用。

def no_share(attr):
    def _no_share(decorated):
        def func(self, request, *args, **kwargs):
            """Don't let them in if it's shared"""

            if kwargs.get(attr, True):
                from django.http import Http404
                raise Http404('not availiable for sharing')

            return decorated(request, *args, **kwargs)
        return func
    return _no_share
4

Bruce Eckel写的这篇文章,Li0liQ提到的,应该能帮助你理解这个问题。带参数和不带参数的装饰器在行为上有些不同。最大的区别在于,当你传递参数时,__call__方法在__init__时只会被调用一次,并且它应该返回一个函数,这个函数会在每次调用被装饰的函数时被调用。而如果没有参数,__call__方法会在每次调用被装饰的函数时都被调用。

这对你意味着什么呢?对于一个@no_arg_decorator,__init__和__call__的调用方式和@decorator('with','args')的调用方式是不同的。

这里有两个装饰器,可能会对你有帮助。只要你总是带着括号使用@no_share_on(...)装饰器,你就可以只用这个。

def sharing_check(view, attr_name, request, *args, **kwargs):
    if kwargs.get(attr_name, True):
        from django.http import Http404
        raise Http404('not availiable for sharing')

    return view(request, *args, **kwargs)

class no_share(object):
    """A decorator w/o arguments.  Usage:
    @no_share
    def f(request):
        ...
    """
    def __init__(self, view):
        self.view = view

    def __call__(self, request, *args, **kwargs):
        return sharing_check(self.view, 'sharing', request, *args, **kwargs)

class no_share_on(object):
    """A decorator w/ arguments.  Usage:
    @no_share_on('something')
    def f(request):
        ...
    --OR--
    @no_share_on()
    def g(request):
        ...
    """
    def __init__(self, attr_name='sharing'):
        self.attr_name = attr_name

    def  __call__(self, view):
        def wrapper_f(request, *args, **kwargs):
            return sharing_check(view, self.attr_name, request, *args, **kwargs)
7

我希望布鲁斯·埃克尔的这篇文章能对你有所帮助。

更新: 根据这篇文章,你的代码应该像这样:

class no_share(object):
    def __init__(self, arg1):
        self.arg1 = arg1

    def __call__(self, f):
        """Don't let them in if it's shared"""

        # Do something with the argument passed to the decorator.
        print 'Decorator arguments:', self.arg1

        def wrapped_f(request, *args, **kwargs):
            if kwargs.get('shared', True):
                from django.http import Http404
                raise Http404('not availiable for sharing')
            f(request, *args, **kwargs)            
        return wrapped_f

可以根据需要使用:

@no_share('prefs')
def prefs(request, [...])

撰写回答