了解此 Python 装饰器的工作原理
Understanding how this Python Decorator works
我一直在研究如何创建自己的装饰器,并给出了以下示例:
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))
我不明白那段代码的逻辑。
- 如何在函数内部引用函数 (
wrapper.count
)?
- wrapper 如何在定义 wrapper 之前拥有方法计数?
- 不应该在我每次调用 foo() 时执行行 wrapper.count = 0 吗?
wrapper.count()
只是 wrapper
命名空间中的一个变量。它在 wrapper.count = 0
的包装函数外部定义,并在装饰函数时执行。所以它不是wrapper()的方法,而是全局到wrapper()
的变量。每次调用foo()
,都会执行wrapper()
函数,即增加计数器。
您可以将注释 # Call the function ...
替换为 func()
对函数的实际调用,以便它显示 foo()
.
的打印输出
装饰器不是那么容易理解的。这是一个 link,可能会帮助您了解到底发生了什么:https://realpython.com/primer-on-python-decorators/
- How can I reference a function inside itself (wrapper.count)?
函数体仅在您调用时执行。到那时,函数已经定义好了,这就是使之成为可能的原因。以下不会给你任何错误,除非你调用它:
>>> def foo():
... non_existing_function()
...
并且当你进入foo
的正文时,foo
已经被定义,所以你可以引用它。这也是使递归调用成为可能的原因。
- How wrapper has the method count before wrapper is defined?
问题也可以是“如何在初始化之前增加 wrapper.count
?”
但同样,我们可以用同样的方式回答这个问题:因为函数体在我们调用它们之前不会执行,所以 wrapper.count
在 wrapper.count += 1
.
之前被初始化为 0
- Shouldn't the line wrapper.count = 0 be executed everytime I call foo()?
让我们看看发生了什么。您写了:
@counter
def foo():
print('calling foo()')
这只是一个语法糖:
foo = counter(foo)
现在,我们以 foo
作为参数调用 counter
函数。 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
用人类语言来说,
- 定义一个名为
wrapper
的函数,它接受未知数量的位置和关键字参数
- 将
0
指定为名为 count
的属性,用于 wrapper
函数
- return
wrapper
给来电者
并且当我们将结果赋值给 foo
函数时,我们实际上已经将 wrapper
赋给了 foo
。所以当我们调用foo
的时候,实际上是在调用wrapper
。 wrapper.count = 0
行在 wrapper
函数之外,所以它不会 运行 每次我们调用 foo
.
最后,我强烈建议您观看 PyCon talk by Reuven M. Lerner 关于装饰器的精彩内容。
编辑:我没有阅读包装器的主体,这实际上证明您并不真的需要知道包装器内部是什么。我的解释仍然正确。但是,正如 中所建议的那样,您的包装器可能应该 return func(*args,**kwargs)
而不是 wrapper.count
包装器不正确。 return wrapper.count
是错误的。如评论所述,它应该 return 带有参数的函数调用的结果;否则,foo 将 return 每次被调用的次数而不是它的实际结果。
def counter(func):
def wrapper(*args, **kwargs): # "wrapper" function now exists
wrapper.count += 1 # count doesn't exist yet, but will when wrapper is called.
return func(*args,**kwargs) # call the wrapped function and return result
wrapper.count = 0 # init the attribute on the function
return wrapper
# Every time "counter" is used, it defines a *different* wrapper function
# with its own localized "count" variable initialized to zero.
@counter
def foo(a,b):
print(f'foo({a,b})') # demonstrate that foo was called with the correct arguments.
return a + b
@counter
def bar(a,b):
print(f'bar({a,b})') # demonstrate that bar was called with the correct arguments.
return a * b
print(foo(1,2))
print(foo(b=3,a=4)) # keywords work, too.
print(bar(5,b=6))
print(f'foo() was called {foo.count} times.')
print(f'bar() was called {bar.count} times.')
输出:
foo((1, 2))
3
foo((4, 3))
7
bar((5, 6))
30
foo() was called 2 times.
bar() was called 1 times.
我一直在研究如何创建自己的装饰器,并给出了以下示例:
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))
我不明白那段代码的逻辑。
- 如何在函数内部引用函数 (
wrapper.count
)? - wrapper 如何在定义 wrapper 之前拥有方法计数?
- 不应该在我每次调用 foo() 时执行行 wrapper.count = 0 吗?
wrapper.count()
只是 wrapper
命名空间中的一个变量。它在 wrapper.count = 0
的包装函数外部定义,并在装饰函数时执行。所以它不是wrapper()的方法,而是全局到wrapper()
的变量。每次调用foo()
,都会执行wrapper()
函数,即增加计数器。
您可以将注释 # Call the function ...
替换为 func()
对函数的实际调用,以便它显示 foo()
.
装饰器不是那么容易理解的。这是一个 link,可能会帮助您了解到底发生了什么:https://realpython.com/primer-on-python-decorators/
- How can I reference a function inside itself (wrapper.count)?
函数体仅在您调用时执行。到那时,函数已经定义好了,这就是使之成为可能的原因。以下不会给你任何错误,除非你调用它:
>>> def foo():
... non_existing_function()
...
并且当你进入foo
的正文时,foo
已经被定义,所以你可以引用它。这也是使递归调用成为可能的原因。
- How wrapper has the method count before wrapper is defined?
问题也可以是“如何在初始化之前增加 wrapper.count
?”
但同样,我们可以用同样的方式回答这个问题:因为函数体在我们调用它们之前不会执行,所以 wrapper.count
在 wrapper.count += 1
.
- Shouldn't the line wrapper.count = 0 be executed everytime I call foo()?
让我们看看发生了什么。您写了:
@counter
def foo():
print('calling foo()')
这只是一个语法糖:
foo = counter(foo)
现在,我们以 foo
作为参数调用 counter
函数。 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
用人类语言来说,
- 定义一个名为
wrapper
的函数,它接受未知数量的位置和关键字参数 - 将
0
指定为名为count
的属性,用于wrapper
函数 - return
wrapper
给来电者
并且当我们将结果赋值给 foo
函数时,我们实际上已经将 wrapper
赋给了 foo
。所以当我们调用foo
的时候,实际上是在调用wrapper
。 wrapper.count = 0
行在 wrapper
函数之外,所以它不会 运行 每次我们调用 foo
.
最后,我强烈建议您观看 PyCon talk by Reuven M. Lerner 关于装饰器的精彩内容。
编辑:我没有阅读包装器的主体,这实际上证明您并不真的需要知道包装器内部是什么。我的解释仍然正确。但是,正如 func(*args,**kwargs)
而不是 wrapper.count
包装器不正确。 return wrapper.count
是错误的。如评论所述,它应该 return 带有参数的函数调用的结果;否则,foo 将 return 每次被调用的次数而不是它的实际结果。
def counter(func):
def wrapper(*args, **kwargs): # "wrapper" function now exists
wrapper.count += 1 # count doesn't exist yet, but will when wrapper is called.
return func(*args,**kwargs) # call the wrapped function and return result
wrapper.count = 0 # init the attribute on the function
return wrapper
# Every time "counter" is used, it defines a *different* wrapper function
# with its own localized "count" variable initialized to zero.
@counter
def foo(a,b):
print(f'foo({a,b})') # demonstrate that foo was called with the correct arguments.
return a + b
@counter
def bar(a,b):
print(f'bar({a,b})') # demonstrate that bar was called with the correct arguments.
return a * b
print(foo(1,2))
print(foo(b=3,a=4)) # keywords work, too.
print(bar(5,b=6))
print(f'foo() was called {foo.count} times.')
print(f'bar() was called {bar.count} times.')
输出:
foo((1, 2))
3
foo((4, 3))
7
bar((5, 6))
30
foo() was called 2 times.
bar() was called 1 times.