Python 装饰器练习指南:包装函数
装饰器的想法很小,回报却很大:函数可以传给另一个函数,并被它的返回值替换。理解这一点后,日志、计时、缓存和校验就能变成可复用的层,而不是到处复制的代码。
函数也是普通对象
可以把函数赋给名字、作为参数传递,也可以返回它。装饰器正是利用这些能力:接收原函数,并返回一个通常会调用它的可调用对象。
def greet():
return "hello"
def call(func):
return func()
print(call(greet))传递函数
构造包装函数
内部函数通过闭包记住原函数,外层装饰器返回这个内部函数。在被装饰函数调用之前,包装函数里的代码不会运行。
def announce(func):
def wrapper():
return "Before: " + func()
return wrapper
@announce
def greet():
return "hello"
print(greet())包装函数稍后运行
读懂 @ 简写
@ 行会在函数定义之后应用。@announce 放在 greet 上方,表示 greet = announce(greet)。此时名字 greet 已指向包装函数,所以包装函数必须显式调用原函数。
def upper(func):
def wrapper():
return func().upper()
return wrapper
@upper
def word():
return "hi"
print(word())名字被包装函数替换
用 wraps 保留元数据
没有额外处理时,包装函数的 __name__ 会变成 wrapper。functools.wraps 会复制原函数名、文档字符串和相关属性。调试、文档和检查函数的工具都依赖这些信息。
from functools import wraps
def upper(func):
@wraps(func)
def wrapper():
return func().upper()
return wrapper
@upper
def word():
return "hi"
print(word.__name__)保留原函数身份
转发参数
没有参数的包装函数只能包装没有参数的函数。用 *args 和 **kwargs 接收原函数能接受的值,再原样转发。包装函数自己的逻辑应放在调用原函数之外。
def announce(func):
def wrapper(*args, **kwargs):
return "Result: " + str(func(*args, **kwargs))
return wrapper
@announce
def add(a, b):
return a + b
print(add(2, 3))通用包装函数
你的练习清单
- 记住装饰器要返回可调用对象。
- 外层函数要返回包装函数。
- 在包装函数内部调用原函数。
- 用 functools.wraps 保留元数据。
- 函数有参数时转发 *args 和 **kwargs。
常见问题
装饰器可以接收参数吗?
可以。这时需要再加一层:先接收参数,再返回真正的装饰器。
@ 会立刻改变函数吗?
它会在定义时替换名字。包装函数的函数体要等到新名字被调用时才运行。
装饰器只能用于函数吗?
类也可以被装饰,但先从函数开始。只要一个可调用对象能被另一个可调用对象替换,同样的思路就适用。
练习装饰器
12 道题,覆盖包装函数、@ 语法、functools.wraps 和参数转发。
继续阅读: