如何将带参数的Python decorator实现为一个类?

2024-05-14 23:41:59 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试实现一个接受一些参数的装饰器。通常,带参数的装饰器实现为双嵌套闭包,如下所示:

def mydecorator(param1, param2):
    # do something with params
    def wrapper(fn):
        def actual_decorator(actual_func_arg1, actual_func_arg2):
            print("I'm decorated!")

            return fn(actual_func_arg1, actual_func_arg2)

        return actual_decorator

    return wrapper

但就我个人而言,我不喜欢这种方法,因为它是非常不可读和难以理解。你知道吗

所以我就这样结束了:

class jsonschema_validate(object):
    def __init__(self, schema):
        self._schema = schema

    def __call__(self, fn):
        self._fn = fn

        return self._decorator

    def _decorator(self, req, resp, *args, **kwargs):
        try:
            jsonschema.validate(req.media, self._schema, format_checker=jsonschema.FormatChecker())
        except jsonschema.ValidationError as e:
            _log.exception('Validation failed: %r', e)

            raise errors.HTTPBadRequest('Bad request')

        return self._fn(req, resp, *args, **kwargs)

其思想非常简单:在实例化时,我们只捕获decorator参数,在调用时,我们捕获decorated函数并返回decorator实例的方法,该方法是绑定的。绑定它是很重要的,因为在decorator调用时,我们希望访问self,并将所有信息存储在其中。你知道吗

然后我们用它来上课:

class MyResource(object):
    @jsonschema_validate(my_resource_schema)
    def on_post(self, req, resp):
        pass

不幸的是,这种方法不起作用。问题是,在decorator调用时,我们会释放装饰实例的上下文,因为在装饰时(定义类时),装饰方法没有绑定。绑定稍后在属性访问时发生。但是现在我们已经有了decorator的绑定方法(jsonschema_validate._decorator),并且self是隐式传递的,它的值不是MyResource实例,而是jsonschema_validate实例。我们不想丢失这个值,因为我们想在decorator调用时访问它的属性。最后,当调用self._fn(req, resp, *args, **kwargs)时,它会导致TypeError,并抱怨“缺少所需的位置参数resp”,因为传入的reqarg变为MyResource.on_postself”,所有参数实际上都“移位”。你知道吗

那么,有没有办法将decorator实现为一个类而不是一堆嵌套函数呢?你知道吗

注意

当我第一次尝试将decorator实现为简单类时很快就失败了,我立即返回到嵌套函数。似乎正确实现的类方法更难理解和纠结,但我还是想找到解决方案,从中获得乐趣。你知道吗


Tags: 实例方法self参数returnschemadef装饰
1条回答
网友
1楼 · 发布于 2024-05-14 23:41:59

这很有趣!谢谢你发布这个问题。你知道吗

编写一个不接受参数的简单装饰器相当容易,但是将其扩展到一个调用了三次的类则更具挑战性。我选择使用functools.partial来解决这个问题。你知道吗

from functools import partial, update_wrapper
from unittest import TestCase, main


class SimpleDecorator(object):

    def __new__(cls, func, **params):
        self = super(SimpleDecorator, cls).__new__(cls)
        self.func = func
        self.params = params
        return update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        args, kwargs = self.before(*args, **kwargs)
        return self.after(self.func(*args, **kwargs))

    def after(self, value):
        return value

    def before(self, *args, **kwargs):
        return args, kwargs


class ParamsDecorator(SimpleDecorator):

    def __new__(cls, **params):
        return partial(super(ParamsDecorator, cls).__new__, cls, **params)


class DecoratorTestCase(TestCase):

    def test_simple_decorator(self):
        class TestSimpleDecorator(SimpleDecorator):

            def after(self, value):
                value *= 2
                return super().after(value)

        @TestSimpleDecorator
        def _test_simple_decorator(value):
            """Test simple decorator"""
            return value + 1

        self.assertEqual(_test_simple_decorator.__name__, '_test_simple_decorator')
        self.assertEqual(_test_simple_decorator.__doc__, 'Test simple decorator')
        self.assertEqual(_test_simple_decorator(1), 4)

    def test_params_decorator(self):
        class TestParamsDecorator(ParamsDecorator):

            def before(self, value, **kwargs):
                value *= self.params['factor']
                return super().before(value, **kwargs)

        @TestParamsDecorator(factor=3)
        def _test_params_decorator(value):
            """Test params decorator"""
            return value + 1

        self.assertEqual(_test_params_decorator.__name__, '_test_params_decorator')
        self.assertEqual(_test_params_decorator.__doc__, 'Test params decorator')
        self.assertEqual(_test_params_decorator(2), 7)

如您所见,我选择了带有钩子的设计来修改方法中的参数和响应。希望在大多数情况下,这样可以避免接触__call____new__。你知道吗

在返回partial之后,我想不出将params附加到ParamsDecorator的方法,所以我不得不选择将它放入SimpleDecorator中,但不使用它。你知道吗

我认为这在保持内容扁平化而不是嵌套化方面做得很好。我也喜欢这个可以为您处理functools.wraps,所以您不必担心在这些decorator中包含这个。以这种方式编写decorator的缺点是,您现在引入了一个新模块,您需要安装或维护该模块,然后在每次编写decorator时导入该模块。你知道吗

相关问题 更多 >

    热门问题