试图理解一个Python包装器

问题描述 投票:0回答:1

对于下面的函数,我想了解的是

i. 为什么是 wrapper.count = 0 在封装函数下面初始化?为什么不在def counter(func)下面初始化?而且为什么包装函数的counter不重设 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()')
python function wrapper
1个回答
4
投票

装饰器出错了,在包装函数里面你需要。

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

为什么 wrapper.count = 0 在包装函数下面初始化?

因为如果你在包装函数内部这样做,那么它总是会重设 wrapper.count0. 除非你检查它还没有被定义。(我在答案的最后给出了一个例子)。

为什么不在下面初始化 def counter(func)?

因为那里没有定义封装函数。所以解释器会抱怨。

而为什么 wrapper.count 复位 wrapper.count0 因为它是在封装函数下面执行的?

因为这个语句在你用 @counter 装饰器,并且它不会在每次调用 foo() 功能。

我想知道什么是 wrapper.count?

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

为什么不直接初始化一个普通的变量 count 相对于 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

2
投票

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

这段代码有一个主要问题。包装器实际上并没有像它应该的那样调用被包装的函数。而不是 return func,它只是返回函数对象,它应该改为

return func(*args, **kwargs)

作为 @warvariuc指出其中一个可能的原因是,作者没有或不知道。nonlocal,让你可以访问包围的命名空间。

我认为一个更合理的原因是,你希望能够访问计数器。函数是具有可变异字典的一级对象,你可以对它们进行任意的属性分配和访问。你可以对它们进行任意的属性分配和访问。可以方便地检查 foo.count 几次通话后,否则当初为什么要维护它?

原因是 wrapper.counter 的方式进行初始化,简单来说就是 wrapper 不存在于本地命名空间中,直到 def 语句运行来创建它。一个新的函数对象是由内部的 def 每逢 counter. 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

0
投票

为什么不直接初始化一个正常的变量数,而不是初始化一个 variable.count

我猜测这种模式最早出现在Python 2中,其中 nonlocal 语句不可用。在我看来,这段代码的作者只是试图像在C语言中一样模拟静态变量( https:/stackoverflow.coma279586248296。 ).

因为如果你试图使用一个在函数的顶层声明的普通变量 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()')
© www.soinside.com 2019 - 2024. All rights reserved.