当前位置:首页>python>30天入门Python(基础篇)——第24天:Python中的装饰器详解

30天入门Python(基础篇)——第24天:Python中的装饰器详解

  • 2026-10-11 08:42:59
30天入门Python(基础篇)——第24天:Python中的装饰器详解

30天入门Python(基础篇)——第24天:Python中的装饰器详解

📅 学习日期:第24天 | ⏱️ 预计用时:60分钟 | 📊 难度等级:⭐⭐⭐⭐


🎯 学习目标

  • • 理解装饰器的本质和作用
  • • 掌握装饰器的基本语法和使用方法
  • • 学会编写带参数的装饰器
  • • 理解 functools.wraps 的作用
  • • 掌握类装饰器的使用方法
  • • 能够编写多个装饰器叠加的复杂场景

欢迎大家关注此公众号,后台留言"python书籍"可免费获取【Python办公自动化高清PDF】电子书一本

此外小庄推荐一本适合于新手\小白入手一本 Python基础书籍,欢迎大家订阅,也感谢大家支持,我才有更新的动力


一、什么是装饰器?

1.1 装饰器的概念

装饰器(Decorator)是 Python 中一个非常强大且优雅的特性。从本质上讲,装饰器就是一个函数,它接受一个函数作为参数,返回一个新的函数。装饰器允许我们在不修改原有函数代码的情况下,为其添加额外的功能。

打个比方:装饰器就像给手机套上一个手机壳。手机本身的功能不变,但手机壳给它增加了防摔、美观等额外特性。

1.2 装饰器的设计原则

装饰器遵循 开放-封闭原则(Open-Closed Principle):

  • • 对扩展开放:可以给函数添加新功能
  • • 对修改封闭:不需要修改原函数的代码

二、函数装饰器的基础

2.1 最简单的装饰器

defmy_decorator(func):
"""一个简单的装饰器"""
defwrapper():
print("在函数执行之前")
        func()
print("在函数执行之后")
return wrapper

@my_decorator
defsay_hello():
print("Hello!")

# 调用被装饰的函数
say_hello()

输出:

在函数执行之前
Hello!
在函数执行之后

2.2 装饰器的执行流程

@my_decorator
def say_hello():
    ...

等价于:

defsay_hello():
    ...
say_hello = my_decorator(say_hello)

2.3 原理解析

让我们逐步拆解上面的装饰器:

  1. 1. 定义阶段:Python 解释器读到 @my_decorator 时,会立即调用 my_decorator(say_hello),并将返回值重新赋值给 say_hello
  2. 2. 调用阶段:当我们调用 say_hello() 时,实际调用的是 wrapper() 函数
  3. 3. 执行阶段:wrapper() 内部调用了原始的 func()(即原 say_hello),并在其前后添加了额外逻辑

三、带参数的函数装饰器

3.1 装饰带参数的函数

上面的装饰器只能装饰无参数的函数。实际开发中,我们需要处理带参数的函数:

defmy_decorator(func):
defwrapper(*args, **kwargs):
print(f"调用函数 {func.__name__},参数: {args}, {kwargs}")
        result = func(*args, **kwargs)
print(f"函数 {func.__name__} 执行完毕,返回值: {result}")
return result
return wrapper

@my_decorator
defadd(a, b):
return a + b

@my_decorator
defgreet(name, age=20):
print(f"你好, {name}! 你今年 {age} 岁。")

# 测试
add(3, 5)
greet("小明", age=25)

输出:

调用函数 add,参数: (3, 5), {}
函数 add 执行完毕,返回值: 8
调用函数 greet,参数: ('小明',), {'age': 25}
你好, 小明! 你今年 25 岁。
函数 greet 执行完毕,返回值: None

3.2 关键点:*args 和 **kwargs

  • • *args:接收所有位置参数,打包成元组
  • • **kwargs:接收所有关键字参数,打包成字典
  • • 这种写法使装饰器可以适配任意参数签名的函数

四、带参数的装饰器

有时候我们需要给装饰器本身传递参数,比如指定重试次数、日志级别等。

4.1 语法结构

带参数的装饰器需要三层嵌套:

defrepeat(times):
"""装饰器工厂:控制函数重复执行次数"""
defdecorator(func):
defwrapper(*args, **kwargs):
for i inrange(times):
print(f"第 {i + 1} 次执行:")
                result = func(*args, **kwargs)
return result
return wrapper
return decorator

@repeat(3)
defsay_hi():
print("Hi!")

say_hi()

输出:

第 1 次执行:
Hi!
第 2 次执行:
Hi!
第 3 次执行:
Hi!

4.2 执行流程解析

@repeat(3)
defsay_hi():
    ...

等价于:

defsay_hi():
    ...
say_hi = repeat(3)(say_hi)
#          ↑ 先执行    ↑ 再执行
  1. 1. repeat(3) 先执行,返回 decorator 函数
  2. 2. decorator(say_hi) 再执行,返回 wrapper 函数
  3. 3. 最终 say_hi 指向 wrapper

4.3 实战:带参数的重试装饰器

import time

defretry(max_retries=3, delay=1):
"""重试装饰器:函数失败时自动重试"""
defdecorator(func):
defwrapper(*args, **kwargs):
for attempt inrange(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"第 {attempt + 1} 次尝试失败: {e}")
if attempt < max_retries - 1:
print(f"等待 {delay} 秒后重试...")
                        time.sleep(delay)
print("所有重试均失败")
return wrapper
return decorator

# 模拟一个可能失败的函数
import random

@retry(max_retries=3, delay=0.5)
deffetch_data():
if random.random() < 0.7:  # 70% 概率失败
raise ConnectionError("网络连接失败")
return"数据获取成功!"

result = fetch_data()

五、functools.wraps 的作用

5.1 问题引入

使用装饰器后,原函数的元信息(函数名、文档字符串等)会丢失:

defmy_decorator(func):
defwrapper(*args, **kwargs):
"""wrapper 的文档字符串"""
return func(*args, **kwargs)
return wrapper

@my_decorator
defgreet(name):
"""向用户打招呼"""
print(f"Hello, {name}!")

print(f"函数名: {greet.__name__}")       # 输出: wrapper
print(f"文档字符串: {greet.__doc__}")     # 输出: wrapper 的文档字符串

5.2 解决方案:@wraps

from functools import wraps

defmy_decorator(func):
    @wraps(func)  # ← 关键:保留原函数的元信息
defwrapper(*args, **kwargs):
"""wrapper 的文档字符串"""
return func(*args, **kwargs)
return wrapper

@my_decorator
defgreet(name):
"""向用户打招呼"""
print(f"Hello, {name}!")

print(f"函数名: {greet.__name__}")       # 输出: greet
print(f"文档字符串: {greet.__doc__}")     # 输出: 向用户打招呼

5.3 最佳实践

所有装饰器都应该使用 @wraps,这是一个良好的编程习惯。它能:

  • • 保留原函数名 __name__
  • • 保留原函数文档 __doc__
  • • 保留原函数的其他元信息
  • • 对调试、日志、自省等场景非常重要

六、多个装饰器的叠加

6.1 装饰器链

一个函数可以同时被多个装饰器装饰:

defdecorator_a(func):
    @wraps(func)
defwrapper(*args, **kwargs):
print("[A] 执行前")
        result = func(*args, **kwargs)
print("[A] 执行后")
return result
return wrapper

defdecorator_b(func):
    @wraps(func)
defwrapper(*args, **kwargs):
print("[B] 执行前")
        result = func(*args, **kwargs)
print("[B] 执行后")
return result
return wrapper

@decorator_a
@decorator_b
defhello():
print("Hello World!")

hello()

输出:

[A] 执行前
    [B] 执行前
        Hello World!
    [B] 执行后
[A] 执行后

6.2 执行顺序

装饰器的执行顺序是从内到外(从下到上):

@decorator_a   # 最外层,最后执行
@decorator_b   # 内层,先执行
deffunc():
    ...

等价于:

func = decorator_a(decorator_b(func))

七、类装饰器

装饰器不仅可以是函数,也可以是类。

7.1 类作为装饰器

classCounter:
"""统计函数调用次数的装饰器"""
def__init__(self, func):
self.func = func
self.count = 0

def__call__(self, *args, **kwargs):
self.count += 1
print(f"第 {self.count} 次调用")
returnself.func(*args, **kwargs)

@Counter
defsay_hello():
print("Hello!")

say_hello()
say_hello()
say_hello()
print(f"总共调用了 {say_hello.count} 次")

输出:

第 1 次调用
Hello!
第 2 次调用
Hello!
第 3 次调用
Hello!
总共调用了 3 次

7.2 关键点

  • • 类作为装饰器时,需要实现 __call__ 方法
  • • __init__ 接收被装饰的函数
  • • __call__ 在每次调用函数时执行

八、实战案例

8.1 计时装饰器

import time
from functools import wraps

deftimer(func):
"""计算函数执行时间的装饰器"""
    @wraps(func)
defwrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
print(f"{func.__name__} 执行耗时: {end - start:.6f} 秒")
return result
return wrapper

@timer
defslow_function():
"""模拟一个耗时操作"""
    time.sleep(1)
return"完成"

slow_function()

8.2 日志装饰器

import logging
from functools import wraps

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')

deflog(func):
"""记录函数调用的日志装饰器"""
    @wraps(func)
defwrapper(*args, **kwargs):
        logging.info(f"调用 {func.__name__}({', '.join(map(str, args))}, {kwargs})")
        result = func(*args, **kwargs)
        logging.info(f"{func.__name__} 返回 {result}")
return result
return wrapper

@log
defmultiply(a, b):
return a * b

multiply(6, 7)

8.3 缓存装饰器

from functools import wraps

defcache(func):
"""简单的缓存装饰器"""
    cache_dict = {}

    @wraps(func)
defwrapper(*args):
if args in cache_dict:
print(f"从缓存中获取: {args}")
return cache_dict[args]
        result = func(*args)
        cache_dict[args] = result
print(f"计算并缓存: {args}")
return result
return wrapper

@cache
deffibonacci(n):
"""计算斐波那契数列"""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(5))

九、常见错误和注意事项

9.1 忘记返回 wrapper

# ❌ 错误:没有返回 wrapper
defbad_decorator(func):
defwrapper(*args, **kwargs):
print("before")
        func(*args, **kwargs)
print("after")
# 这里缺少 return wrapper

# ✅ 正确
defgood_decorator(func):
defwrapper(*args, **kwargs):
print("before")
return func(*args, **kwargs)
return wrapper  # ← 必须返回

9.2 忘记返回函数结果

# ❌ 错误:没有返回 result
defwrapper(*args, **kwargs):
    func(*args, **kwargs)  # 返回值被丢弃

# ✅ 正确
defwrapper(*args, **kwargs):
return func(*args, **kwargs)  # 保留返回值

9.3 类方法装饰器中的 self

装饰类方法时,第一个参数是 self,要注意:

defmethod_logger(func):
    @wraps(func)
defwrapper(self, *args, **kwargs):
print(f"调用 {self.__class__.__name__}.{func.__name__}")
return func(self, *args, **kwargs)
return wrapper

十、总结

知识点
关键内容
装饰器本质
接受函数作为参数,返回新函数的高阶函数
基本语法
@decorator
 放在函数定义上方
参数处理
使用 *args, **kwargs 适配任意参数
装饰器参数
需要三层嵌套函数
元信息保留
使用 @wraps(func)
多个装饰器
从下到上(从内到外)执行
类装饰器
实现 __init__ 和 __call__ 方法

核心要点回顾

  1. 1. 装饰器 = 函数包装器,可以在不修改原函数的情况下添加功能
  2. 2. @wraps 是必备,保留函数名和文档字符串
  3. 3. *args, **kwargs 是标配,让装饰器可以装饰任何函数
  4. 4. 三层嵌套是带参数装饰器的标准写法
  5. 5. 多个装饰器叠加时,注意执行顺序是从下到上

十一、练习题

练习 1:编写权限检查装饰器

编写一个装饰器,检查用户是否有权限调用某函数。如果用户名不在允许列表中,打印 "无权限"。

练习 2:编写性能监控装饰器

编写一个装饰器,统计并打印函数的执行时间,如果超过指定阈值则发出警告。

练习 3:编写 HTML 标签装饰器

编写一个装饰器,将函数的字符串返回值包裹在指定的 HTML 标签中(如 <p>, <div>)。

# 期望效果
@html_tag("p")
defget_text():
return"Hello World"

print(get_text())  # 输出: <p>Hello World</p>

参考答案

练习 1 参考答案
from functools import wraps

defrequire_permission(allowed_users):
defdecorator(func):
        @wraps(func)
defwrapper(user, *args, **kwargs):
if user notin allowed_users:
print(f"用户 '{user}' 无权限调用 {func.__name__}")
returnNone
return func(user, *args, **kwargs)
return wrapper
return decorator

@require_permission(["admin", "manager"])
defdelete_record(user, record_id):
print(f"用户 {user} 删除了记录 {record_id}")

delete_record("admin", 101)
delete_record("guest", 102)
练习 2 参考答案
import time
from functools import wraps

deftime_limit(max_seconds):
defdecorator(func):
        @wraps(func)
defwrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            elapsed = time.time() - start
if elapsed > max_seconds:
print(f"⚠️ 警告: {func.__name__} 耗时 {elapsed:.2f}s,超过阈值 {max_seconds}s")
else:
print(f"✅ {func.__name__} 耗时 {elapsed:.4f}s")
return result
return wrapper
return decorator
练习 3 参考答案
from functools import wraps

defhtml_tag(tag_name):
defdecorator(func):
        @wraps(func)
defwrapper(*args, **kwargs):
            result = func(*args, **kwargs)
returnf"<{tag_name}>{result}</{tag_name}>"
return wrapper
return decorator

@html_tag("p")
defget_text():
return"Hello World"

print(get_text())  # <p>Hello World</p>

🎉 恭喜你完成了第24天的学习!装饰器是 Python 中非常重要的高级特性,掌握它将让你的代码更加优雅和模块化。

📌 下一天预告:第25天 —— Python中模块与包详解

最新文章

随机文章