如何将多个 @staticmethod 装饰器组合成一个

问题描述 投票:0回答:1

我想将多个@statimemethod装饰器(我们称它们为

decorator_1
decorator_2
等)组合成一个@staticmethod
combined_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
,据我所知,这是需要的。

有什么想法吗?

python python-decorators python-3.10
1个回答
0
投票

简短回答:

最简单的解决方法是将装饰器移到类之外。不再有

@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()
© www.soinside.com 2019 - 2024. All rights reserved.