我想将多个@statimemethod装饰器(我们称它们为
decorator_1
,decorator_2
等)组合成一个@staticmethodcombined_decorator
。
对于我的用例,我需要装饰器能够访问类对象,因此我无法在类之外声明我的装饰器。例如,考虑一个类异常计数器装饰器,只要装饰方法
self.exception_count
引发异常,它就会增加 my_func(self)
变量。例如:
import functools
class Example:
def __init__(self):
self.exception_count = 0
@staticmethod
def count_exception(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except Exception as e:
print(f"caught: {e}")
self.exception_count += 1
return wrapper
@count_exception
def foo(self):
raise RuntimeError("exception!")
x = Example()
# prints: "exception count: 0"
print(f"exception count: {x.exception_count}")
x.foo()
x.foo()
# prints: "exception count: 2"
print(f"exception count: {x.exception_count}")
在这个玩具示例中,只有一个装饰器和一个装饰器方法。就我而言,我有很多装饰器,很多装饰方法,并且总是使用相同的装饰模式。所以不要写:
@decorator_1
@decorator_2
# ...
@decorator_X
def foo():
pass
我想要一个
@combined_decorator
。
我尝试了以下实现,但不起作用:
class MyClass:
@staticmethod
def _decorator1(func):
def wrapper(self, *args, **kwargs):
print("static decorator 1")
return func(self, *args, **kwargs)
return wrapper
@staticmethod
def _decorator2(func):
def wrapper(self, *args, **kwargs):
print("static decorator 2")
return func(self, *args, **kwargs)
return wrapper
@staticmethod
def _combined_decorator(func):
@MyClass._decorator1
@MyClass._decorator2
def wrapper(self, *args, **kwargs):
return func(self, *args, **kwargs)
return wrapper
@_combined_decorator
def my_func(self):
print("my func")
x = MyClass()
x.my_func()
这里的问题是
NameError: name 'MyClass' is not defined
在 _combined_decorator
的定义中
我有几种变体:使用
@classmethod
、将 @MyClass._decoratorX
更改为 @_decoratorX
等。但似乎没有任何效果。
我使用的是3.10,所以我认为
@staticmethod
应该可以在同一个类中调用其他@staticmethod
,据我所知,这是需要的。
有什么想法吗?
简短回答:
最简单的解决方法是将装饰器移到类之外。不再有
@staticmethod
,不再有问题。
如果我真的想将内容保留在类定义中,另一种解决方法是将装饰器移动到内层以获取
self
并调用 @self.decorator_X
,如下所示:
@staticmethod
def _combined_decorator(func):
def wrapper(self, *args, **kwargs):
@self._decorator1
@self._decorator2
def inner(self, *args, **kwargs):
return func(self, *args, **kwargs)
return inner(self, *args, **kwargs)
return wrapper
一个缺点是每次函数调用时都会生成一个新的修饰函数。这可能会导致奇怪的副作用。
最后一个解决方案是将装饰器定义保留在类内部,但将装饰移到类定义之后,如下所示。
长答案:
我最初实现的问题来自这样一个事实:虽然
@staticmethod
方法可以在运行时引用其他静态方法,但它不适用于 @
装饰,因为此类装饰函数是在类定义期间评估的(因此类对象尚未定义)。当你想到 @my_decorator
只是语法糖时,这一点就变得更清楚了:
class Test:
@staticmethod
def my_decorator(func):
pass
def my_func(self):
pass
my_func = my_decorator(my_func)
虽然 python 允许您在类体内调用
my_decorator
,但类 Test
尚不存在,因此 my_decorator
无法调用 Test.my_other_decorator
。
但是我不明白的一件事是,你可以写
my_func = my_decorator(my_func)
或my_other_func = my_other_decorator(my_other_func)
,但my_decorator
不能调用my_other_decorator
。
承认这个限制,另一个(丑陋的)解决方法是在类之外添加装饰,如下所示:
class Test:
@staticmethod
def direct_decorator(func):
def wrapper(self, *args, **kwargs):
print("direct_decorator")
return func(self, *args, **kwargs)
return wrapper
@staticmethod
def indirect_decorator(func):
return Test.direct_decorator(func)
""" Same as:
def hello_direct(self):
print("hello_direct")
hello_direct = direct_decorator(hello_direct)
"""
@direct_decorator
def hello_direct(self):
print("hello_direct")
# @indirect_decorator # does not work
def hello_indirect(self):
print("hello_indirect")
# Test is defined, so not problem here
Test.hello_indirect = Test.indirect_decorator(Test.hello_indirect)
x = Test()
x.hello_direct()
x.hello_indirect()