gpt4 book ai didi

python - 如何将带有参数的Python装饰器实现为类?

转载 作者:行者123 更新时间:2023-12-01 07:15:09 25 4
gpt4 key购买 nike

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

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)

这个想法非常简单:在实例化时,我们只捕获装饰器参数,在调用时,我们捕获装饰器函数并返回装饰器实例的绑定(bind)方法。绑定(bind)它很重要,因为在装饰器调用时我们想要访问 self所有信息都存储在其中。

然后我们在某个类上使用它:

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

不幸的是,这种方法不起作用。问题是,在装饰器调用时,我们会丢失装饰实例的上下文,因为在装饰时(定义类时)装饰方法未绑定(bind)。绑定(bind)稍后在属性访问时发生。但此时我们已经有了装饰器的绑定(bind)方法( jsonschema_validate._decorator )和 self是隐式传递的,它的值不是 MyResource例如,而是 jsonschema_validate实例。我们不想失去这个self value 因为我们想在装饰器调用时访问它的属性。最终结果是TypeError调用self._fn(req, resp, *args, **kwargs)时提示“缺少必需的位置参数‘resp’”,因为传入 req arg 变为 MyResource.on_postself ”并且所有参数有效地“转移”。

那么,有没有办法将装饰器实现为一个类而不是一堆嵌套函数?

注意

由于我第一次尝试将装饰器实现为简单类很快就失败了,因此我立即恢复到嵌套函数。似乎正确实现的类方法更加难以阅读和纠结,但我无论如何都想找到解决方案,以获取乐趣。

更新

终于找到解决方案,请参阅我自己的答案。

最佳答案

这很有趣!感谢您提出这个问题。

编写一个不带参数的简单装饰器非常容易,但将其扩展到一个随后被调用三次的类则更具挑战性。我选择使用 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)

正如您所看到的,我选择了带有钩子(Hook)的设计,用于修改方法中的参数和响应。希望大多数时候这可以避免需要触摸 __call____new__

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

我认为这可以很好地保持内容平坦而不是嵌套。我还喜欢它可以为您处理 functools.wraps ,因此您不必担心将其包含在这些装饰器中。以这种方式编写装饰器的缺点是您现在引入了一个新模块,您需要安装或维护该模块,然后在每次编写装饰器时导入该模块。

关于python - 如何将带有参数的Python装饰器实现为类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58013015/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com