欢迎光临
我们一直在努力

Python 装饰器专项练习:6 道编程题从入门到精通

配套专栏: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 秒

💡 编程思路

这道题考察 闭包三大要素:嵌套函数、引用外部变量、返回内部函数。

  • make_counter:外层函数创建 count 变量,内层函数通过 nonlocal 修改它。每次调用 make_counter() 创建独立的闭包实例。
  • make_timer:外层函数创建 start_time 变量,内层函数读取它计算耗时。通过 nonlocal 更新起始时间。
  • 属性附加:通过 counter.count = count 将状态暴露为属性。
  • 🖥️ 参考代码

    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:

  • wrapper(*args, **kwargs):接收任意参数,保证装饰器的通用性。
  • @wraps(func):保留原函数的 __name__、__doc__ 等元信息。
  • 日志格式:记录调用时间、函数名、参数和返回值。
  • 带参数装饰器:三层嵌套,外层接收 level 等配置。
  • 🖥️ 参考代码

    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 ===\”)

    赞(0)
    未经允许不得转载:171主机测评 » Python 装饰器专项练习:6 道编程题从入门到精通
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址