了解这个 Python 装饰器的工作原理

Fed*_*ega 5 wrapper python-3.x python-decorators

我一直在研究如何创建自己的装饰器,并给出了以下示例:

def counter(func):
  def wrapper(*args, **kwargs):
    wrapper.count += 1
    # Call the function being decorated and return the result
    return wrapper.count
  wrapper.count = 0
  # Return the new decorated function
  return wrapper

# Decorate foo() with the counter() decorator
@counter
def foo():
  print('calling foo()')
  
foo()
foo()

print('foo() was called {} times.'.format(foo.count))
Run Code Online (Sandbox Code Playgroud)

我不明白那段代码的逻辑。

  1. 如何在自身内部引用函数 ( wrapper.count)?
  2. 在定义包装器之前包装器如何具有方法计数?
  3. 每次调用 foo() 时不应该执行行 wrapper.count = 0 吗?

Aso*_*cia 4

  1. 如何引用自身内部的函数(wrapper.count)?

函数体仅在调用时才会执行。到那时,该函数已经被定义,因此这使得它成为可能。以下不会给你任何错误,除非你调用它:

>>> def foo():
...     non_existing_function()
...
Run Code Online (Sandbox Code Playgroud)

每当您进入 的正文时foofoo已经定义,因此您可以引用它。这也是递归调用成为可能的原因。

  1. 在定义包装器之前,包装器如何拥有方法计数?

问题也可能是“我如何wrapper.count在初始化之前增加它?

但同样,我们可以用同样的方式回答这个问题:因为函数体在我们调用它们之前不会被执行,所以在wrapper.count之前初始化为 0 wrapper.count += 1

  1. 每次我调用 foo() 时,不应该执行wrapper.count = 0 行吗?

让我们看看发生了什么。你写:

@counter
def foo():
  print('calling foo()')
Run Code Online (Sandbox Code Playgroud)

这只是一个语法糖:

foo = counter(foo)
Run Code Online (Sandbox Code Playgroud)

现在,我们以参数调用counter函数。foo有什么counter作用?

def counter(func):
  def wrapper(*args, **kwargs):
    wrapper.count += 1
    # Call the function being decorated and return the result
    return wrapper.count
  wrapper.count = 0
  # Return the new decorated function
  return wrapper
Run Code Online (Sandbox Code Playgroud)

用人类语言来说,

  • 定义一个名为 的函数wrapper,该函数采用未知数量的位置参数和关键字参数
  • 分配为以函数命名的0属性countwrapper
  • 返回wrapper给调用者

当我们将结果分配回函数时foo,我们实际上已经分配wrapper给了foo. 所以当我们打电话时foo,我们实际上是在打电话wrapper。该行wrapper.count = 0位于wrapper函数外部,因此它不会在我们每次调用时运行foo

最后,我强烈建议您观看Reuven M. Lerner关于装饰器的精彩 PyCon 演讲。

编辑:我没有阅读包装器的正文,这实际上证明您并不真正需要知道包装器内部的内容。我的解释仍然是正确的。但是,正如@Mark Tolonen 的回答中所建议的,你的包装器可能应该返回func(*args,**kwargs) wrapper.count