在 Python 中,是不是可以使用相同的装饰器来装饰类和非类方法?
Posted
技术标签:
【中文标题】在 Python 中,是不是可以使用相同的装饰器来装饰类和非类方法?【英文标题】:In Python, is it possible to decorate both a class and non-class method with the same decorator?在 Python 中,是否可以使用相同的装饰器来装饰类和非类方法? 【发布时间】:2012-09-03 22:27:00 【问题描述】:我有一个简单的异常记录装饰器,当我的脚本抛出异常时,它可以方便地给自己发送电子邮件。
def logExceptions(func):
def wrapper():
try:
func()
except Exception, e:
logger.exception(e)
return wrapper
但是,如果我想装饰一个类方法,我必须修改 wrapper() 来取一个'self',否则我会得到以下错误:
TypeError: wrapper() takes no arguments (1 given)
当然,此时我不能用它来装饰任何非类方法,因为那样就会出现这个错误:
TypeError: wrapper() takes exactly 1 argument (0 given)
有没有一种干净的方法来解决这个问题?谢谢=)
【问题讨论】:
我建议编辑您的问题以使用更典型的术语“函数”和“方法”(或“实例方法”)而不是“类方法”和“非类方法”。使用“类方法”是令人困惑的,因为在 Python 中,类方法是一种用@classmethod
装饰器装饰的方法,并且将类本身(通常是 cls
)而不是实例(self
)作为其第一个参数。
【参考方案1】:
通常的做法是定义您的包装器,使其接受*args
和**kwargs
并将它们传递给它包装的函数。这样它可以包装任何函数。
另外,我的印象是,你所说的“类方法”就是 Python 所说的“实例方法”,而你所说的“非类方法”就是 Python 所说的“函数”。 Python 中的“非类方法”(例如,实例方法)采用 self
参数。
【讨论】:
是的,实际上类方法 OP 可能意味着实例方法或实际类方法(“classmethod”装饰器),而静态方法(“staticmethod”装饰器)适合第二种描述的行为。但总的来说,很难说 OP 是想问实例方法与独立函数,还是实例/类方法与静态方法。【参考方案2】:实例方法classmethod
和staticmethod
之间的区别
首先要注意:静态方法和类方法也是函数,所以标准的函数规则大多适用于它们。我了解您的问题是关于 static 方法(没有传递额外的参数)和 class 方法(在第一个参数中接收类)之间的区别:
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
证明(或者更确切地说是不言自明的例子)在这里:
>>> t = Test()
>>> t.standard_method()
((<__main__.Test object at 0x0000000002B47CC0>,), )
>>> t.class_method()
((<class '__main__.Test'>,), )
>>> t.static_method()
((), )
如您所见,传递的参数列表因您选择的方法类型而异。您面临的问题是参数数量可变。
解决方案
有一个解决方案 - 使用参数解包:
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
返回的函数将接受与修饰函数相同数量的参数。
所以这两个例子都可以工作:
@some_decorator
def say_hello():
print 'hello'
@some_decorator
def say_something(something):
print something
附录
为了给你一个完整的例子,如果你能使用这样的结构会很好(注意functools.wraps
的用法):
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
原因在documentation for functools.wraps()
中列出:它保留了函数名和文档字符串,从而有效地使包装器看起来像一个被包装的函数(这有时很有用)。
【讨论】:
@Jan-PhilipGehrcke:没问题,我很高兴能帮上忙 :)【参考方案3】:装饰的替代方法是使用sys.excepthook
,它是一个回调,可对所有未捕获的异常进行操作,您可以为其分配自定义日志记录函数。这样做的好处是,您不需要破坏(更重要的是,跟踪)您对记录异常感兴趣的每个函数。
【讨论】:
以上是关于在 Python 中,是不是可以使用相同的装饰器来装饰类和非类方法?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Python 中没有 @override 装饰器来帮助提高代码的可读性? [关闭]