在函数和方法中使用相同的装饰器(带参数)

21 投票
5 回答
7426 浏览
提问于 2025-04-15 13:39

我一直在尝试创建一个装饰器,可以同时用于 Python 中的函数和方法。单独这样做其实不难,但当我想创建一个需要参数的装饰器时,就变得复杂了。

class methods(object):
    def __init__(self, *_methods):
        self.methods = _methods

    def __call__(self, func): 
        def inner(request, *args, **kwargs):
            print request
            return func(request, *args, **kwargs)
        return inner

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        new_func = self.func.__get__(obj, type)
        return self.__class__(new_func)

上面的代码可以正确地包装函数或方法,但在方法的情况下,request 参数是它所操作的实例,而不是第一个非 self 的参数。

有没有办法判断这个装饰器是应用在函数上还是方法上,然后做出相应的处理呢?

5 个回答

5

因为你已经在定义一个 __get__ 方法来让你的装饰器在绑定方法上使用,你可以传一个标志,告诉它这个装饰器是用在方法上还是函数上。

class methods(object):
    def __init__(self, *_methods, called_on_method=False):
        self.methods = _methods
        self.called_on_method

    def __call__(self, func):
        if self.called_on_method:
            def inner(self, request, *args, **kwargs):
                print request
                return func(request, *args, **kwargs)
        else:
            def inner(request, *args, **kwargs):
                print request
                return func(request, *args, **kwargs)
        return inner

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        new_func = self.func.__get__(obj, type)
        return self.__class__(new_func, called_on_method=True)
5

装饰器总是应用在一个函数对象上。如果你让装饰器 print 打印它的参数类型,你就能确认这一点;而且它通常也会返回一个函数对象(这本身就是一个带有正确 __get__ 的装饰器!)不过也有一些例外。

也就是说,在下面的代码中:

class X(object):

  @deco
  def f(self): pass

deco(f) 是在类体内被调用的,而在你还在那里的时候,f 是一个函数,而不是方法类型的实例。(这个方法是在后面当 f 被作为 X 或其实例的属性访问时,通过 f__get__ 制造并返回的)。

也许你可以更好地解释一下你想要装饰器的一个简单用法,这样我们可以提供更多帮助…?

编辑:这同样适用于带参数的装饰器,也就是说:

class X(object):

  @deco(23)
  def f(self): pass

那么在类体内调用的是 deco(23)(f),当 f 被作为参数传递给 deco(23) 返回的可调用对象时,f 仍然是一个函数对象,而那个可调用对象也应该返回一个函数对象(通常是这样的——不过也有例外;-)。

21

接下来我们来详细讲讲__get__的方法。这种方法可以变得更通用,变成一个装饰器的装饰器。

class _MethodDecoratorAdaptor(object):
    def __init__(self, decorator, func):
        self.decorator = decorator
        self.func = func
    def __call__(self, *args, **kwargs):
        return self.decorator(self.func)(*args, **kwargs)
    def __get__(self, instance, owner):
        return self.decorator(self.func.__get__(instance, owner))

def auto_adapt_to_methods(decorator):
    """Allows you to use the same decorator on methods and functions,
    hiding the self argument from the decorator."""
    def adapt(func):
        return _MethodDecoratorAdaptor(decorator, func)
    return adapt

这样一来,你的装饰器就可以自动适应它被使用的环境了。

def allowed(*allowed_methods):
    @auto_adapt_to_methods
    def wrapper(func):
        def wrapped(request):
            if request not in allowed_methods:
                raise ValueError("Invalid method %s" % request)
            return func(request)
        return wrapped
    return wrapper

要注意的是,包装函数在每次调用函数时都会被执行,所以不要在这里做一些耗时的操作。

装饰器的使用方法:

class Foo(object):
    @allowed('GET', 'POST')
    def do(self, request):
        print "Request %s on %s" % (request, self)

@allowed('GET')
def do(request):
    print "Plain request %s" % request

Foo().do('GET')  # Works
Foo().do('POST') # Raises

撰写回答