Python 装饰器计数函数调用

Python decorators count function call

我正在回忆一些我还没有得到的 python 功能,我正在学习 this python tutorial 并且有一个我不完全理解的例子。这是关于装饰器计算函数调用的,代码如下:

def call_counter(func):
    def helper(x):
        helper.calls += 1
        return func(x)
    helper.calls = 0
    return helper

@call_counter
def succ(x):
    return x + 1

if __name__ == '__main__':
    print(succ.calls)
    for i in range(10):
        print(succ(i))
    print(succ.calls)

我不明白的是为什么我们要增加函数包装器 (helper.calls += 1) 的调用而不是函数调用自身,为什么它真的有效?

What I don't get here is why do we increment the calls of the function wrapper (helper.calls += 1) instead of the function calls itself, and why does it actually working?

我想让它成为一个通用的装饰器。你可以这样做

def succ(x):
    succ.calls += 1
    return x + 1

if __name__ == '__main__':
    succ.calls = 0
    print(succ.calls)
    for i in range(10):
        print(succ(i))
    print(succ.calls)

效果很好,但您需要将 .calls +=1 放入您想要应用的每个函数中,并在 运行 任何函数之前将其初始化为 0。如果你有一大堆你想计算的功能,这绝对更好。另外,它在定义时将它们初始化为 0,这很好。

据我所知,它之所以有效,是因为它用装饰器中的 helper 函数替换了函数 succ(每次装饰函数时都会重新定义),所以 succ = helpersucc.calls = helper.calls。 (当然,名称助手只在装饰器的命名空间内定义)

这有意义吗?

据我了解(如果我错了请纠正我)你程序执行的顺序是:

  1. 注册call_function
  2. 注册succ
  3. 在注册 succ 函数解释器时找到装饰器,因此它会执行 call_function.
  4. 你的函数 returns 一个对象,它是一个函数 (helper)。并添加到此对象字段 calls.
  5. 现在您的函数 succ 已分配给 helper。所以当你调用你的函数时,你实际上是在调用 helper 函数,包装在一个装饰器中。因此,您添加到辅助函数的每个字段都可以通过寻址 succ 在外部访问,因为这 2 个变量指的是同一事物。
  6. 所以当你调用 succ() 时,如果你调用 helper(*args, **argv)
  7. 基本上是一样的

看看这个:

def helper(x):
    helper.calls += 1
    return 2
helper.calls = 0

def call_counter(func):
    return helper

@call_counter
def succ(x):
    return x + 1

if __name__ == '__main__':
    print(succ == helper)  # prints true.

关于装饰器要记住的重要一点是,装饰器是一个 函数 ,它将一个函数作为参数,return 又是另一个函数。 returned 值 - 另一个函数 - 是调用原始函数名称时将调用的值。

这个模型可以很简单:

def my_decorator(fn):
    print("Decorator was called")
    return fn

在这种情况下,returned 函数与传入函数相同。但这通常不是你要做的。通常,您 return 一个完全不同的函数,或者您 return 一个以某种方式链接或包装原始函数的函数。

在您的示例中,这是一个非常常见的模型,您有一个内部函数 returned:

def helper(x):
    helper.calls += 1
    return func(x)

此内部函数调用原始函数 (return func(x)),但它也会增加调用计数器。

这个内部函数作为 "replacement" 被插入,用于装饰的任何函数。因此,当您的模块 foo.succ() 函数被查找时,结果是对由装饰器编辑的内部辅助函数 return 的引用。该函数递增调用计数器,然后调用最初定义的 succ 函数。

装饰函数时,您“替换”了包装器的函数。

在这个例子中,装饰后,当你调用succ时,你实际上是在调用helper。因此,如果您正在计算通话次数,则必须增加 helper 次通话。

您可以通过检查修饰函数的属性 _name_ 来检查装饰函数后名称是否与包装器绑定:

def call_counter(func):
    def helper(*args, **kwargs):
        helper.calls += 1
        print(helper.calls)
        return func(*args, **kwargs)
    helper.calls = 0
    return helper

@call_counter
def succ(x):
    return x + 1

succ(0)
>>> 1
succ(1)
>>> 2
print(succ.__name__)
>>> 'helper'
print(succ.calls)
>>> 2

带有 Class 装饰器的示例

当您使用 Class 装饰器 装饰一个函数时,每个函数都有自己的 call_count。这是 OOP 的简单性。每次调用CallCountDecorator对象都会增加自己的call_count属性并打印它。

class CallCountDecorator:
    """
    A decorator that will count and print how many times the decorated function was called
    """

    def __init__(self, inline_func):
        self.call_count = 0
        self.inline_func = inline_func

    def __call__(self, *args, **kwargs):
        self.call_count += 1
        self._print_call_count()
        return self.inline_func(*args, **kwargs)

    def _print_call_count(self):
        print(f"The {self.inline_func.__name__} called {self.call_count} times")


@CallCountDecorator
def function():
    pass


@CallCountDecorator
def function2(a, b):
    pass


if __name__ == "__main__":
    function()
    function2(1, b=2)
    function()
    function2(a=2, b=3)
    function2(0, 1)

# OUTPUT
# --------------
# The function called 1 times
# The function2 called 1 times
# The function called 2 times
# The function2 called 2 times
# The function2 called 3 times