试图理解 Python 包装器

Trying to understand a Python wrapper

对于下面的函数,我正在尝试理解

我。为什么 wrapper.count = 0 在包装函数下面初始化?为什么不在 def counter(func) 下面初始化?为什么 wrapper.count 不将 wrapper.count 重置为 0,因为它的 运行 在包装函数下面?

我想了解什么是 wrapper.count?为什么不只初始化一个普通变量 count 而不是 wrapper.count

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

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

Why not just initialise a normal variable count as opposed to variable.count

我的猜测是这种模式首先出现在 Python 2 中,其中 nonlocal 语句不可用。在我看来,该片段的作者只是试图模拟 C 语言中的静态变量()。

因为如果您尝试使用在函数 counter 的顶层声明的普通变量,您将无法在 wrapper.

中对其赋值

如果你把 count 放在 counter 下面你会把它变成全局的,所以它会在装饰器的所有实例之间共享,这可能不是期望的行为:

count = 0

def counter(func):

  def wrapper(*args, **kwargs):
    global count
    count += 1
    return func(*args, **kwargs)

  return wrapper

@counter
def foo():
  print('calling foo()')

这里是 nonlocal (Python 3+) 的版本:

def counter(func):

  def wrapper(*args, **kwargs):
    nonlocal count
    count += 1
    # Call the function being decorated and return the result
    return func(*args, **kwargs)

  count = 0
  # Return the new decorated function
  return wrapper

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

装饰器出错,您需要在包装器函数内部:

return func(*args, **kwargs)  # instead of `return func`

Why is wrapper.count = 0 initialised below the wrapper function?

因为如果您在包装函数中执行此操作,那么它将始终将 wrapper.count 的值重置为 0。除非您检查它是否尚未定义。 (我在回答的最后给出了一个例子。)

Why not initialised below def counter(func)?

因为那里没有定义包装函数。所以口译员会抱怨的。

And why doesn't the wrapper.count reset the wrapper.count to 0 since it is executed below the wrapper function?

因为当你用@counter装饰器包装一个函数时,这条语句只执行一次,而不会在你每次调用foo()函数时都执行。

And I'm trying to understand what is wrapper.count?

这是一个函数属性。或多或少类似于 C++ 等函数中的 static 变量

Why not just initialise a normal variable count as opposed to wrapper.count?

因为那将是一个局部变量,它会在每次调用时将 count 重置为 0。


还有另一种方法可以在包装函数中定义 wrapper.count = 0。所以现在你不需要在 wrapper 函数之外定义它。

def counter(func):
  def wrapper(*args, **kwargs):
    if not hasattr(wrapper, 'count'):
        wrapper.count = 0
    wrapper.count += 1
    return func(*args, **kwargs)
  return wrapper

在高层次上,装饰函数维护着它被调用次数的计数器。

代码存在一个主要问题。包装器实际上并没有像它应该的那样调用包装函数。而不是 return func,它只是 returns 函数对象,它应该是

return func(*args, **kwargs)

作为 ,一个可能的原因是作者没有或不知道 nonlocal,它允许您访问封闭的命名空间。

我认为一个更合理的原因是您希望能够访问柜台。函数是具有可变字典的 first-class 对象。您可以分配和访问它们的任意属性。调用几次后检查foo.count可能很方便,否则为什么要首先维护它?

wrapper.counter 被初始化的原因很简单, wrapperdef 语句 运行 之前不存在于本地命名空间中它。每次 运行 counter 时,内部 def 都会创建一个新的函数对象。 def 通常是一个赋值,每次你 运行 它都会创建一个函数对象。

关于您显示的代码的另一个小问题是 foo.__name__ 将在装饰后 wrapper 而不是 foo。为了减轻这种情况,并使其更忠实地模仿原始功能,您可以使用 functools.wraps,它是装饰器包装器的装饰器。您的代码将如下所示:

from functools import wraps

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

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

现在你可以做

>>> foo.__name__
'foo'
>>> foo()
calling foo()
>>> foo()
calling foo()
>>> foo()
calling foo()
>>> foo.count
3