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

Posted

技术标签:

【中文标题】对函数和方法使用相同的装饰器(带参数)【英文标题】:Using the same decorator (with arguments) with functions and methods 【发布时间】:2010-11-20 06:49:08 【问题描述】:

我一直在尝试创建一个可以与 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 参数。

有没有办法判断装饰器是否应用于函数而不是方法,并进行相应处理?

【问题讨论】:

当然,现在最好的解决方案是使用wrapt 【参考方案1】:

扩展__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

【讨论】:

您应该将update_wrapper(self, func) 添加到_MethodDecoratorAdaptor.__init__开始(其中update_wrapper 来自functools 模块)。这使得生成的装饰器在它们装饰的函数/可调用对象上保留自定义属性,同时保持它们的可组合性。 我发现这种方法只在某些情况下有效,当它不起作用时,调试起来非常困难。 groups.google.com/group/django-developers/msg/f36976f5cfbcbeb3 @spookylukey 实际上在 Django 中的处理方式也很干净。 @Juanlu001 Django 是如何处理这个问题的? @Satoru.Logic 上面链接的电子邮件和源代码解释了它。【参考方案2】:

装饰器总是应用于函数对象——拥有装饰器print它的参数类型,您将能够确认;它通常也应该返回一个函数对象(它已经是一个带有正确__get__!- 的装饰器),尽管后者有例外。

即在代码中:

class X(object):

  @deco
  def f(self): pass

deco(f) 在类体内被调用,当你还在的时候,f 是一个函数,而不是一个方法类型的实例。 (该方法在f__get__ 中制造并返回,稍后将f 作为X 的属性或其实例进行访问)。

也许您可以更好地解释一下您想要用于装饰者的玩具用途,以便我们可以提供更多帮助...?

编辑:这也适用于带参数的装饰器,即

class X(object):

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

那么在类主体中调用的是deco(23)(f)f 作为参数传递给任何可调用的deco(23) 返回时仍然是一个函数对象,并且该可调用对象仍应返回一个函数对象(通常 - 与例外;-)。

【讨论】:

【参考方案3】:

由于您已经定义了一个 __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)

【讨论】:

【参考方案4】:

我提出的部分(特定)解决方案依赖于异常处理。我正在尝试创建一个装饰器以仅允许某些 HttpRequest 方法,但使其适用于作为视图的函数和作为视图的方法。

所以,这门课会做我想做的事:

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

    def __call__(self, func): 
        @wraps(func)
        def inner(*args, **kwargs):
            try:
                if args[0].method in self.methods:
                    return func(*args, **kwargs)
            except AttributeError:
                if args[1].method in self.methods:
                    return func(*args, **kwargs)
            return HttpResponseMethodNotAllowed(self.methods)
        return inner

以下是两个用例:装饰函数:

@methods("GET")
def view_func(request, *args, **kwargs):
    pass

类的装饰方法:

class ViewContainer(object):
    # ...

    @methods("GET", "PUT")
    def object(self, request, pk, *args, **kwargs):
        # stuff that needs a reference to self...
        pass

有没有比使用异常处理更好的解决方案?

【讨论】:

【参考方案5】:

这是我发现的一种检测修饰的可调用对象是函数还是方法的通用方法:

import functools

class decorator(object):

  def __init__(self, func):
    self._func = func
    self._obj = None
    self._wrapped = None

  def __call__(self, *args, **kwargs):
    if not self._wrapped:
      if self._obj:
        self._wrapped = self._wrap_method(self._func)
        self._wrapped = functools.partial(self._wrapped, self._obj)
      else:
        self._wrapped = self._wrap_function(self._func)
    return self._wrapped(*args, **kwargs)

  def __get__(self, obj, type=None):
    self._obj = obj
    return self

  def _wrap_method(self, method):
    @functools.wraps(method)
    def inner(self, *args, **kwargs):
      print('Method called on :'.format(type(self).__name__))
      return method(self, *args, **kwargs)
    return inner

  def _wrap_function(self, function):
    @functools.wraps(function)
    def inner(*args, **kwargs):
      print('Function called:')
      return function(*args, **kwargs)
    return inner

示例用法:

class Foo(object):
  @decorator
  def foo(self, foo, bar):
    print(foo, bar)

@decorator
def foo(foo, bar):
  print(foo, bar)

foo(12, bar=42)      # Function called: 12 42
foo(12, 42)          # Function called: 12 42
obj = Foo()
obj.foo(12, bar=42)  # Method called on Foo: 12 42
obj.foo(12, 42)      # Method called on Foo: 12 42

【讨论】:

以上是关于对函数和方法使用相同的装饰器(带参数)的主要内容,如果未能解决你的问题,请参考以下文章

基于 Python 类的装饰器,带有可以装饰方法或函数的参数

带参数的装饰器

在 Python 中,如何获取带参数传递给装饰器的函数名称?

python基础 带参数以及返回值的函数装饰器

Flask视图:视图函数,类视图,蓝图使用方法整理

TypeScript装饰器