试图理解 Python 包装器
Posted
技术标签:
【中文标题】试图理解 Python 包装器【英文标题】:Trying to understand a Python wrapper 【发布时间】:2020-08-30 14:13:40 【问题描述】:对于下面的功能,我正在努力理解
我。为什么wrapper.count = 0
在包装函数下面初始化?为什么不在 def counter(func) 下面初始化?为什么 wrapper.count 不将 wrapper.count
重置为 0,因为它在 wrapper 函数下方运行?
我想了解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()')
【问题讨论】:
" 为什么不在 def counter(func) 下面初始化" 因为wrapper
还没有定义。
【参考方案1】:
装饰器有错误,你需要在包装函数内部:
return func(*args, **kwargs) # instead of `return func`
为什么
wrapper.count = 0
在包装函数下面初始化?
因为如果您在包装函数内部执行此操作,那么它将始终将 wrapper.count
的值重置为 0
。除非您检查它是否尚未定义。 (我在回答的最后给出了一个例子。)
为什么不在
def counter(func)
下面初始化?
因为这里没有定义包装函数。所以口译员会抱怨的。
为什么
wrapper.count
不将wrapper.count
重置为0
,因为它是在包装函数下面执行的?
因为当你用@counter
装饰器包装一个函数时,这条语句只执行一次,而且它不会在你每次调用foo()
函数时执行。
我想了解
wrapper.count
是什么?
这是一个函数属性。或多或少类似于 C++ 等函数中的 static
变量。
为什么不直接初始化一个普通变量
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
【讨论】:
@MadPhysicist 已修复 另外,hasattr
比 in __dict__
更健壮
很好地解决了所有问题。最后一个挑剔。在 C++ 中与静态的类比有点不确定。 Python 函数是第一类对象,是 FunctionType 的实例,因此可以像任何其他对象一样具有属性。
@MadPhysicist 是的,你是对的。我已经在上下文中解释了这个概念到这里的使用。它就像一个计数器,每次调用后都不会重置,而是保存之前的值。就像我们在 C++ 中通过在函数中定义 static
变量一样。
@warvariuc。你真的在乎他的消息来源是什么吗?还是你在乎他比你得更多分?【参考方案2】:
在高层次上,装饰函数维护一个计数器,记录它被调用的次数。
代码存在一个主要问题。包装器实际上并没有按应有的方式调用包装的函数。而不是return func
,它只返回函数对象,它应该是
return func(*args, **kwargs)
作为@warvariuc points out,一个可能的原因是作者没有或不知道nonlocal
,它可以让您访问封闭的命名空间。
我认为更合理的原因是您希望能够访问柜台。函数是具有可变字典的一流对象。您可以分配和访问它们的任意属性。打几个电话后检查foo.count
可能会很方便,否则为什么要维护它呢?
wrapper.counter
以这种方式初始化的原因仅仅是 wrapper
不存在于本地命名空间中,直到运行 def
语句来创建它。每次运行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
【讨论】:
【参考方案3】:为什么不初始化一个普通的变量计数而不是
variable.count
我的猜测是这种模式首先出现在 Python 2 中,其中 nonlocal
语句不可用。在我看来,sn-p 的作者只是试图模拟 C 语言中的静态变量(https://***.com/a/279586/248296)。
因为如果您尝试使用在函数 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()')
【讨论】:
如果我正确理解目的,非本地将无济于事 我认为使用 nonlocal 会产生相同的效果。请参阅答案的更新。 @MadPhysicist 我已经发布了一个答案,说明为什么您可能希望以原始方式进行操作,而不管非本地情况如何。您无法轻松访问它创建的闭包,使您的计数器纯粹供内部使用。以上是关于试图理解 Python 包装器的主要内容,如果未能解决你的问题,请参考以下文章
通过 python 扩展/包装器传递浮点数组指针 – SndObj-library