【问题标题】:Using a class to operate both as decorator and decorator factory使用一个类作为装饰器和装饰器工厂来操作
【发布时间】:2020-10-04 08:00:09
【问题描述】:

考虑以下装饰器函数,它要么返回装饰函数,要么返回参数化装饰函数:

from functools import wraps, partial, update_wrapper
from inspect import signature

def wrapit(func=None, *, verb='calling'):
    if func is None:  # return a decoratOR
        return partial(wrapit, verb=verb)
    else:  # return a decoratED
        @wraps(func)
        def _func(*args, **kwargs):
            print(f'{verb} {func.__name__} with {args} and {kwargs}')
            return func(*args, **kwargs)
        return _func

演示:

>>> f = lambda x, y=1: x + y
>>> ff = wrapit(verb='launching')(f)
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)
>>>
>>> # but can also use it as a "decorator factory"
>>> @wrapit(verb='calling')
... def f(x, y=1):
...     return x + y
...
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)

类形式可能如下所示:

class Wrapit:
    def __init__(self, func, verb='calling'):
        self.func, self.verb = func, verb
        update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
        return self.func(*args, **kwargs)

但是我们如何让类能够以函数形式所具有的“装饰工厂”模式运行(由if func is None: return partial...实现 我们如何将该技巧集成到装饰器类中?

【问题讨论】:

  • 你可以在__new__做同样的伎俩
  • 不确定你的意思@juanpa.arrivillaga。 __new__ 只上课,那么我如何根据func is None 来调整我所做的事情?

标签: python python-3.x python-decorators


【解决方案1】:

按照 cmets 中的建议,您可以使用 __new__ 方法执行此操作:

class Wrapit:
    def __new__(cls, func=None, *, verb='calling'):
        if func is None:
            return partial(cls,verb=verb)
        self = super().__new__(cls)
        self.func, self.verb = func, verb
        update_wrapper(self, func)
        return self

    def __call__(self, *args, **kwargs):
        print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
        return self.func(*args, **kwargs)

每当您尝试实例化一个类时,都会调用__new__ 方法,并且该方法的返回值用作尝试实例化的结果——即使它不是该类的实例!

【讨论】:

  • 接受您的回答。谢谢。请注意,我稍微编辑了您的答案(将object.__new__(cls) 替换为super(Wrapit, cls).__new__(cls) 等)。如果您不同意,请回复。
  • @thorwhalen 我接受了该更改,但拒绝了将其拆分为两种方法的更改,我认为这不太清楚(我选择了“拒绝并编辑”,因为我不确定是否当我想接受一半的编辑时使用它或“改进编辑”)
【解决方案2】:

我接受了@pppery 的回答,因为……这就是答案。我想在这里扩展答案,展示如何通过在父类中编码逻辑来获得更多的重用。这需要将@pppery 的逻辑分成__new____init__ 方法。

from functools import update_wrapper, partial

class Decorator:
    def __new__(cls, func=None, **kwargs):
        if func is None:
            self = partial(cls, **kwargs)
        else:
            self = super().__new__(cls)
        return update_wrapper(self, func)

    def __init__(self, func=None, **kwargs):
        self.func = func
        for attr_name, attr_val in kwargs.items():
            setattr(self, attr_name, attr_val)

    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)
class Wrapit(Decorator):
    def __new__(cls, func=None, *, verb='calling'):
        return super().__new__(cls, func, verb=verb)

    def __call__(self, *args, **kwargs):
        print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
        return super().__call__(*args, **kwargs)

class AnotherOne(Decorator):
    def __new__(cls, func=None, *, postproc=lambda x: x):
        return super().__new__(cls, func, postproc=postproc)

    def __call__(self, *args, **kwargs):
        return self.postproc(super().__call__(*args, **kwargs))

演示:

>>> f = lambda x, y=1: x + y
>>> 
>>> ff = Wrapit(f, verb='launching')
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff)  == signature(f) 
>>> 
>>> fff = AnotherOne(postproc=str)(f)  # doing it the decorator factory way
>>> assert fff(10) == str(11)
>>> assert signature(fff)  == signature(f)

【讨论】:

  • 谢谢,@pppery。不过我必须说,这不是我的理想版本:子类__new__ 方法仅用于提供子类装饰器工厂的签名,但除此之外不做任何实际工作。我也一直在寻求简化这方面。我将在一个单独的问题中发布它并引导您找到它。
猜你喜欢
  • 2021-07-16
  • 2019-01-01
  • 2019-02-20
  • 1970-01-01
  • 2011-09-03
  • 2021-05-09
  • 1970-01-01
  • 2014-02-18
  • 1970-01-01
相关资源
最近更新 更多