配套专栏:Python 全栈修炼之路 第 14 篇《装饰器——Python 最优雅的语法糖》
难度分布:⭐ → ⭐⭐ → ⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐⭐
核心覆盖:闭包、@wraps、带参数装饰器、类装饰器、多层装饰器叠加、缓存/重试/权限实战
前言
装饰器是 Python 中最强大的语法特性之一,它让我们在不修改原函数代码的前提下,优雅地为函数添加额外功能。本练习精选 6 道编程题,从闭包基础到综合实战,帮你彻底掌握装饰器的精髓。
题目一:闭包计数器与计时器 ⭐
📌 题目描述
利用闭包实现一个函数调用计数器和一个简易计时器:
# 闭包计数器
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
print(counter.count) # 3(查看当前计数)
# 每个计数器独立
counter_b = make_counter()
print(counter_b()) # 1
print(counter()) # 4(互不影响)
# 闭包计时器
timer = make_timer()
timer.start()
time.sleep(0.1)
print(timer.elapsed()) # ≈ 0.1 秒
💡 编程思路
这道题考察 闭包三大要素:嵌套函数、引用外部变量、返回内部函数。
🖥️ 参考代码
import time
from functools import wraps
def make_counter(initial: int = 0):
\”\”\”创建一个独立的计数器闭包。\”\”\”
count = initial
def counter():
nonlocal count
count += 1
return count
# 附加属性
counter.count = count
counter.reset = lambda: _reset()
def _reset():
nonlocal count
count = initial
counter.count = count
return counter
def make_timer():
\”\”\”创建一个计时器闭包。\”\”\”
start_time = None
elapsed_time = 0
def timer():
nonlocal start_time, elapsed_time
timer.start = lambda: _start()
timer.stop = lambda: _stop()
timer.elapsed = lambda: _elapsed()
timer.reset = lambda: _reset()
def _start():
nonlocal start_time
start_time = time.perf_counter()
def _stop():
nonlocal elapsed_time
if start_time is not None:
elapsed_time += time.perf_counter() – start_time
start_time = None
def _elapsed():
nonlocal elapsed_time
total = elapsed_time
if start_time is not None:
total += time.perf_counter() – start_time
return total
def _reset():
nonlocal start_time, elapsed_time
start_time = None
elapsed_time = 0
return timer
def make_averager():
\”\”\”创建一个运行平均值计算器。\”\”\”
numbers = []
total = 0
def averager(value=None):
nonlocal total
if value is not None:
numbers.append(value)
total += value
return total / len(numbers) if numbers else 0
averager.values = lambda: list(numbers)
averager.count = lambda: len(numbers)
return averager
# 测试
if __name__ == \”__main__\”:
print(\”=== 闭包计数器 ===\”)
c1 = make_counter()
print(f\”调用: {
c1()}\”) # 1
print(f\”调用: {
c1()}\”) # 2
print(f\”调用: {
c1()}\”) # 3
c2 = make_counter(10)
print(f\”独立计数器: {
c2()}\”) # 11
print(f\”原计数器: {
c1()}\”) # 4
c1.reset()
print(f\”重置后: {
c1()}\”) # 1
print(\”\\n=== 闭包计时器 ===\”)
t = make_timer()
t.start()
time.sleep(0.05)
print(f\”阶段1耗时: {
t.elapsed():.4f}s\”)
t.stop()
time.sleep(0.05)
t.start()
time.sleep(0.05)
t.stop()
print(f\”总耗时: {
t.elapsed():.4f}s\”)
t.reset()
print(f\”重置后: {
t.elapsed():.4f}s\”)
print(\”\\n=== 运行平均值 ===\”)
avg = make_averager()
avg(10)
avg(20)
avg(30)
print(f\”平均值: {
avg():.1f}\”)
print(f\”数据量: {
avg.count()}\”)
print(\”\\n所有测试通过 ✓\”)
🔗 关联知识点
| 嵌套函数 | 函数内定义函数 |
| nonlocal | 声明使用外部非全局变量 |
| 闭包独立实例 | 每次调用创建独立作用域 |
| __closure__ | 查看闭包引用的变量 |
| 属性附加 | 为闭包函数添加属性 |
题目二:日志记录装饰器 ⭐⭐
📌 题目描述
实现一个通用的日志记录装饰器,记录函数调用信息:
@log_call
def add(a, b):
return a + b
add(3, 5)
# [CALL] add(3, 5)
# [RETURN] add → 8
@log_call(level=\”DEBUG\”)
def greet(name, greeting=\”Hello\”):
return f\”{
greeting}, {
name}!\”
greet(\”Alice\”, greeting=\”Hi\”)
# [DEBUG] greet(name=\’Alice\’, greeting=\’Hi\’)
# [DEBUG] greet → \’Hi, Alice!\’
# 验证元信息保留
print(add.__name__) # add(不是 wrapper)
print(greet.__doc__) # 原文档字符串
💡 编程思路
这道题考察 基础装饰器 + @wraps + *args/**kwargs:
🖥️ 参考代码
import time
import functools
from datetime import datetime
def log_call(func):
\”\”\”记录函数调用信息的装饰器。\”\”\”
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 构建参数字符串
args_str = \”, \”.join(repr(a) for a in args)
kwargs_str = \”, \”.join(f\”{
k}={
v!r}\” for k, v in kwargs.items())
all_args = \”, \”.join(filter(None, [args_str, kwargs_str]))
print(f\”[CALL] {
func.__name__}({
all_args})\”)
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() – start
print(f\”[RETURN] {
func.__name__} → {
result!r} ({
elapsed:.6f}s)\”)
return result
return wrapper
def log_with_level(level=\”INFO\”):
\”\”\”带日志级别的装饰器工厂。\”\”\”
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
args_str = \”, \”.join(
f\”{
a!r}\” for a in args
) + (\”, \” if kwargs else \”\”) + \”, \”.join(
f\”{
k}={
v!r}\” for k, v in kwargs.items()
)
ts = datetime.now().strftime(\”%H:%M:%S\”)
print(f\”[{
level}] {
ts} {
func.__name__}({
args_str})\”)
result = func(*args, **kwargs)
print(f\”[{
level}] {
ts} {
func.__name__} → {
result!r}\”)
return result
return wrapper
return decorator
def log_to_file(filepath: str):
\”\”\”将日志写入文件的装饰器。\”\”\”
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
args_str = \”, \”.join(f\”{
a!r}\” for a in args)
kwargs_str = \”, \”.join(f\”{
k}={
v!r}\” for k, v in kwargs.items())
all_args = \”, \”.join(filter(None, [args_str, kwargs_str]))
result = func(*args, **kwargs)
log_line = (
f\”[{
datetime.now().isoformat()}] \”
f\”{
func.__name__}({
all_args}) → {
result!r}\\n\”
)
with open(filepath, \”a\”, encoding=\”utf-8\”) as f:
f.write(log_line)
return result
return wrapper
return decorator
# 测试
if __name__ == \”__main__\”:
print(\”=== log_call ===\”)



