关于参数:在Python中,是否可以用同一个修饰器来修饰类和非类方法?

In Python, is it possible to decorate both a class and non-class method with the same decorator?

我有一个简单的异常日志修饰器,当脚本抛出异常时,它可以方便地向自己发送电子邮件。

1
2
3
4
5
6
7
8
def logExceptions(func):
   def wrapper():
      try:
         func()
      except Exception, e:
         logger.exception(e)

   return wrapper

但是,如果我想要修饰一个类方法,我必须修改wrapper()以获得一个"self",否则我会得到以下错误:

1
TypeError: wrapper() takes no arguments (1 given)

当然,在这一点上,我不能用它来修饰任何非类方法,因为这样会发生以下错误:

1
TypeError: wrapper() takes exactly 1 argument (0 given)

有没有一个干净的方法来解决这个问题?谢谢=


通常情况下,定义包装器以便它接受*args**kwargs,并将它们传递给它包装的函数。这样它就可以包装任何函数。

另外,我觉得你所说的"类方法"就是python所说的"实例方法",而你所说的"非类方法"就是python所说的"函数"。python中的"非类方法"(例如实例方法)采用self参数。


实例方法classmethodstaticmethod的区别

首先要注意的是:静态方法和类方法都是函数,所以标准函数规则主要应用于它们。我理解您的问题是关于静态方法(没有传递额外参数)和类方法(在第一个参数中接收类)之间的区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Test(object):
    def standard_method(*args, **kwargs):
        # it is instance method (first argument will be instance)
        return args, kwargs

    @classmethod
    def class_method(*args, **kwargs):
        # it is class method (first argument will be class)
        return args, kwargs

    @staticmethod
    def static_method(*args, **kwargs):
        # it is static method (receives only arguments passed explicitly)
        return args, kwargs

证据(或更确切地说是不言自明的例子)如下:

1
2
3
4
5
6
7
>>> t = Test()
>>> t.standard_method()
((<__main__.Test object at 0x0000000002B47CC0>,), {})
>>> t.class_method()
((<class '__main__.Test'>,), {})
>>> t.static_method()
((), {})

如您所见,根据您选择的方法类型,传递的参数列表有所不同。您面临的问题是变量个数的参数。

解决方案

有一个解决方案-使用参数解包:

1
2
3
4
5
6
def some_decorator(func):
    def wrapper(*args, **kwargs):
        # do something here
        # args is a tuple with positional args, kwargs is dict with keyword args
        return func(*args, **kwargs)
    return wrapper

之后,some_decorator返回的函数将接受与修饰函数相同数量的参数。

因此,这两个例子都是有效的:

1
2
3
4
5
6
7
@some_decorator
def say_hello():
    print 'hello'

@some_decorator
def say_something(something):
    print something

附录

为了给您提供完整的示例,最好使用这种结构(注意functools.wraps的用法):

1
2
3
4
5
6
7
8
from functools import wraps
def some_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # do something here
        # args is a tuple with positional args, kwargs is dict with keyword args
        return func(*args, **kwargs)
    return wrapper

原因在functools.wraps()的文档中列出:它保留了函数名和docstring,有效地导致包装器看起来像一个包装函数(有时很有用)。


装饰的另一种选择是使用sys.excepthook,它是一个回调,对所有未捕获的异常进行操作,您可以将自定义日志记录功能分配给这些异常。这样做的好处是,您不需要破坏(更重要的是,要跟踪)您对记录异常感兴趣的每个函数。