如何将这两个类装饰器与 functools.update_wrapper 一起使用?

Posted

技术标签:

【中文标题】如何将这两个类装饰器与 functools.update_wrapper 一起使用?【英文标题】:How to use these two class decorators together with functools.update_wrapper? 【发布时间】:2018-01-22 22:41:16 【问题描述】:

我正在阅读有关装饰器的内容,并尝试将这两个示例混合在一起,使它们成为类装饰器而不是常规函数。第一个只允许您每个参数运行一次函数,第二个计算您运行该函数的次数。它们都可以分开工作,但是当我尝试同时用两者装饰一个简单的功能时,它失败了……或者并没有真正失败,但会打印出意外的错误结果。我做了一些阅读,发现 functools 模块可以提供帮助,但我不确定如何。

from functools import update_wrapper

class Memoize:
    def __init__(self, func):
        self.func = func
        self.memo = dict()
        update_wrapper(self, func)

    def __call__(self, *args):
        if args not in self.memo:
            self.memo[args] = self.func(args)
        else:
            print("cls decorator. You have printed this before")
        return self.memo[args]


class CallCounter:
    def __init__(self, func):
        self.func = func
        self.calls = 0
        self.__name__ = func.__name__
        update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        self.calls += 1
        return self.func(*args, **kwargs)


@Memoize
@CallCounter
def doubleprint(x):
    for elem in x:
        print(elem + " " + elem)


doubleprint('Hello')
doubleprint('Hello')
doubleprint('Hello')
doubleprint('Hello')
doubleprint('Bye')

print(doubleprint.calls)

doubleprint('Hello')

doubleprint('Hello')
doubleprint('Hello')
doubleprint('Hello')
doubleprint('Bye')

print(doubleprint.calls)

【问题讨论】:

【参考方案1】:

默认情况下,update_wrapper 从包装类更新 __dict__。所以Memoize 中的funcCallCounter 中的func 覆盖,这意味着Memoize 直接调用你的doubleprint() 函数,而从不调用CallCounter

class Memoize:
    def __init__(self, func):
        self.func = func
        print(type(self.func))  # <class '__main__.CallCounter'>
        self.memo = dict()
        update_wrapper(self, func)
        print(type(self.func))  # <class 'function'>

您可以通过以下方式解决此问题:

        update_wrapper(self, func, updated=[])

这不会将CallCounter中的__dict__复制到Memoize实例中,但仍会复制__name__, __doc__等。

要访问CallCounter 课程,您需要:

print(doubleprint.__wrapped__.calls)

但您需要上述修复,否则这将始终打印 0(因为它从未被调用)。

【讨论】:

以上是关于如何将这两个类装饰器与 functools.update_wrapper 一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

为两个实体设置多对多关系装饰器与创建第三个实体?

如何在 QML 中将 @pyqtSlot 装饰器与其他装饰器一起使用?

@Patch 装饰器与 pytest 夹具不兼容

Python装饰器与面向切面编程

装饰器与继承

如何让 MobX 装饰器与 Create-React-App v2 一起工作?