将装饰器定义为类内的方法

Posted

技术标签:

【中文标题】将装饰器定义为类内的方法【英文标题】:define a decorator as method inside class 【发布时间】:2018-10-03 01:40:08 【问题描述】:

我正在尝试在我的类中创建一个方法来计算特定函数的完整运行。我想使用一个简单的装饰器。我找到了这个reference 并重写了这个简单的脚本:

class myclass:
    def __init__(self):
        self.cnt = 0

    def counter(function):
        """
        this method counts the number of runtime of a function
        """
        def wrapper(self, **args):
            function(**args)
            self.counter += 1
        return wrapper


@myclass.counter
def somefunc():
    print("hello from somefunc")


if __name__ == "__main__":
    obj = myclass()
    # or if comment @myclass.counter
    # somefunc = myclass.counter(somefunc)
    somefunc()

当然我得到:

TypeError: wrapper() missing 1 required positional argument: 'self'

我尝试将计数器重写为类方法:

class myclass:
    def __init__(self):
        self.cnt = 0

    def counter(self, function):
        """
        this function counts the number of runtime of a function
        """
        def wrapper(**args):
            function(**args)
            self.cnt += 1
        return wrapper


def somefunc():
    print("hello from somefunc")


if __name__ == "__main__":
    obj = myclass()
    somefunc = obj.counter(somefunc)
    for i in range(10):
        somefunc()
        print(obj.cnt)

这很好,但我认为它不是一个有效的装饰器定义。有没有办法在类方法中定义装饰器并将自参数传递给它的函数?还是在类中定义装饰器没用?

编辑:------ 首先,我无法在类方法之外定义装饰。其次,我正在尝试创建一个计划类,该类在固定间隔和特定时间量内运行特定函数(作为输入),因此我需要对其进行计数。

【问题讨论】:

Python decorators count function call 的可能重复项您也可以查看 self__init__ here 是什么 @iam.Carrot 谢谢,但我的装饰器定义必须在我的类(方法或其他)中,因为它必须计入我的日程安排。 it must count for my schedule 到底是什么意思? @iam.Carrot 我正在创建一个计划类,它运行特定函数作为特定时间的输入。我需要计算内部函数的运行时间,然后终止调度。 您是要维护所有实例的计数还是要处理单个实例的计数。例如,如果我创建 10 个实例,并从每个实例调用该方法 5 次,您希望计数为 50 还是仅 5? 【参考方案1】:

所以我能够为你起草一些东西,下面是代码:

def count(func):
    def wrapper(self):
        TestClass.call_count += 1
        func(self)

    return wrapper


class TestClass(object):
    call_count = 0

    @count
    def hello(self):
        return 'hello'


if __name__ == '__main__':
    x = TestClass()
    for i in range(10):
        x.hello()

    print(TestClass.call_count)

为什么将装饰器放在一个类中会导致问题:

在课堂上使用decorator function 并不简单。原因如下:

原因 1 每个类方法都必须接受一个参数self,它是调用函数的类的instance。现在,如果你让装饰器函数接受self 参数,装饰器调用@count 将失败,因为它被转换为count(),而count() 没有传递self 参数,因此出现错误:

TypeError: wrapper() 缺少 1 个必需的位置参数:'self'

原因 2 现在为避免这种情况,您可以通过更改如下声明将 decorator 设为静态:

@staticmethod
def count(func):
    pass

但是你有另一个错误:

TypeError: 'staticmethod' 对象不可调用

这意味着您也不能拥有静态方法。如果类中不能有静态方法,则必须将self 实例传递给该方法,但如果将self 实例传递给它,则@count 装饰器调用将不会传递@987654335 @instance,因此它不起作用。

所以here 是一个很好地解释它的博客,与之相关的问题以及替代方案是什么。

我个人更喜欢使用helper class 来保存我所有可以使用的decorators 的选项,而不是定义它的唯一类。这将使您可以灵活地重用装饰器,而不是按照意识形态重新定义它们

一次编码,一次又一次地重复使用。

【讨论】:

我认为sample function(hello here)的定义必须在类之外,因为它必须是未来的任何东西。 我不这么认为,我会告诉你为什么。装饰器count 是完成这项工作的实际功能。所以,明天你可以有一堆不同的方法,它们都可以装饰成@count,你不必编辑相同的函数。如果有什么我会建议有不同的方法 我不确定,但我的其他类方法必须在类本身,输入函数必须是单个函数或多个函数,我不知道。所以我只需要创建我的类的一个实例并启动计划和类内的计数器变量计数,并且在那里做的所有事情都看我在问题中的第二个例子。 我已经用原因更新了答案。您也可以通过我分享的链接了解更多关于为什么它不可能以及有哪些替代方案。 @MichaelsonB​​ritt 我已经更新了答案。我希望它能解决博客和答案之间的两个陈述中的冲突。我非常感谢您对此的快速提醒。如果我可以进一步改进,请告诉我。【参考方案2】:

您的第二个代码示例在功能上等同于标准装饰器。标准装饰器语法只是同一件事的简写,即重新分配等于闭包(带有预定义参数的函数指针)的函数值,其中闭包是您的装饰器包装器,将原始参数作为其预定义参数。

这是标准语法的等价物。请注意,您需要提前创建计数器类实例。装饰器语法引用该实例,因为它必须指示持有计数器的特定对象,而不仅仅是对象的类:

class myclass:
    def __init__(self):
        self.cnt = 0

    def counter(self,function):
        """
        this method counts the number of runtime of a function
        """
        def wrapper(**args):
            function(self,**args)
            self.cnt += 1
        return wrapper

global counter_object
counter_object = myclass()

@counter_object.counter
def somefunc(self):
    print("hello from somefunc")

if __name__ == "__main__":
    for i in range(10):
        somefunc()
        print(counter_object.cnt)

【讨论】:

以上是关于将装饰器定义为类内的方法的主要内容,如果未能解决你的问题,请参考以下文章

使用装饰器的类内的函数包装器

装饰器类学习小结

将附加参数传递给角度组件内的方法装饰器

如何在类中定义装饰器?

[TimLinux] Python 装饰器

闭包函数,装饰器