当前位置: 首页 > news >正文

python装饰器简单案例实践

一、装饰器是什么?先搞懂核心原理

在讲装饰器之前,我们需要先明确 Python 的一个核心特性:函数是 “一等公民”。这意味着函数可以像变量一样被赋值、作为参数传递,甚至作为其他函数的返回值。而装饰器的本质,就是基于这个特性实现的 “函数包装器”。

1.1 装饰器的定义

装饰器(Decorator)是一个接收函数作为参数,并返回一个新函数的特殊函数。它的核心作用是:在不修改原函数代码的前提下,为原函数添加额外功能(比如日志、计时、权限判断等)。

举个最直观的例子:假设我们有一个计算两数之和的函数 add(),现在需要给它增加 “打印计算日志” 的功能,但不能修改 add() 本身的代码 —— 这时候装饰器就能派上用场。

二、从 0 开始写第一个装饰器

我们先通过 “手动包装” 的方式理解装饰器的逻辑,再过渡到标准的 @ 语法。

2.1 步骤 1:定义原函数

首先,我们有一个简单的原函数 add,用于计算两个数的和:

def add(a, b):"""计算两数之和"""return a + b# 正常调用
print(add(2, 3))  # 输出:5

2.2 步骤 2:定义装饰器函数

我们定义一个 log_decorator 函数,它接收一个函数 func 作为参数,并返回一个新的函数 wrapper(包装函数)。在 wrapper 中,我们会先执行 “打印日志” 的额外逻辑,再调用原函数 func

def log_decorator(func):"""用于添加日志功能的装饰器"""# 定义包装函数,*args 和 **kwargs 用于接收原函数的任意参数def wrapper(*args, **kwargs):# 额外功能:打印日志(记录函数名和参数)print(f"调用函数:{func.__name__},参数:{args}, {kwargs}")# 调用原函数,并获取返回值result = func(*args, **kwargs)# 额外功能:打印返回值print(f"函数 {func.__name__} 的返回值:{result}")# 返回原函数的返回值(保证原函数调用逻辑不变)return result# 返回包装函数return wrapper

2.3 步骤 3:使用装饰器包装原函数

有两种方式可以使用装饰器:手动赋值和 @ 语法糖(推荐)。

方式 1:手动赋值(理解原理)

将原函数 add 作为参数传给 log_decorator,得到包装后的新函数,再赋值给 add(相当于 “替换” 了原函数):

# 用装饰器包装 add 函数
add = log_decorator(add)# 现在调用 add,实际执行的是 wrapper 函数
print(add(2, 3))

执行结果如下,可见额外的日志功能已经生效:

调用函数:add,参数:(2, 3), {}
函数 add 的返回值:5
5
方式 2:@ 语法糖(实战常用)

Python 提供了 @ 符号作为装饰器的语法糖,直接放在原函数定义上方即可,效果和手动赋值完全一致,但代码更简洁:

# 用 @ 语法糖应用装饰器
@log_decorator
def add(a, b):return a + b# 直接调用,自动触发装饰器逻辑
print(add(2, 3))  # 输出结果和上面一致

三、进阶:带参数的装饰器

上面的装饰器是 “固定功能” 的(只能打印固定格式的日志),但实际场景中,我们可能需要让装饰器支持自定义参数(比如让日志输出不同的前缀)。这时候就需要定义带参数的装饰器—— 本质是 “装饰器的工厂函数”。

3.1 带参数装饰器的实现逻辑

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

  1. 最外层函数:接收装饰器的自定义参数,返回一个 “真正的装饰器”(即之前的 log_decorator);

  2. 中间层函数:接收原函数 func,返回包装函数 wrapper

  3. 最内层函数 wrapper:执行额外逻辑和原函数。

3.2 示例:支持自定义日志前缀的装饰器

比如我们需要让日志开头可以自定义前缀(如 [INFO][DEBUG]),实现代码如下:

def log_decorator_with_prefix(prefix):"""带参数的装饰器:支持自定义日志前缀"""# 中间层:真正的装饰器(接收原函数)def actual_decorator(func):def wrapper(*args, **kwargs):# 使用装饰器的自定义参数 prefixprint(f"{prefix} 调用函数:{func.__name__},参数:{args}, {kwargs}")result = func(*args, **kwargs)print(f"{prefix} 函数 {func.__name__} 的返回值:{result}")return resultreturn wrapper# 返回真正的装饰器return actual_decorator

3.3 使用带参数的装饰器

使用时需要在 @ 后传入装饰器的参数(注意括号不能少):

# 应用带参数的装饰器,指定日志前缀为 [INFO]
@log_decorator_with_prefix("[INFO]")
def add(a, b):return a + b# 调用函数
print(add(2, 3))

执行结果如下,日志前缀已按自定义参数显示:

[INFO] 调用函数:add,参数:(2, 3), {}
[INFO] 函数 add 的返回值:5
5

四、实战:装饰器的 3 个常用场景

装饰器的应用非常广泛,以下是 3 个企业开发中高频使用的场景,直接复制代码即可复用。

4.1 场景 1:函数执行时间统计

用于定位耗时函数,优化性能:

import timedef time_decorator(func):"""统计函数执行时间的装饰器"""def wrapper(*args, **kwargs):start_time = time.time()  # 记录开始时间result = func(*args, **kwargs)  # 执行原函数end_time = time.time()  # 记录结束时间# 打印执行时间(保留4位小数)print(f"函数 {func.__name__} 执行时间:{end_time - start_time:.4f} 秒")return resultreturn wrapper# 测试:统计一个耗时操作(比如循环100万次)
@time_decorator
def slow_operation():for i in range(10**6):passslow_operation()  # 输出:函数 slow_operation 执行时间:0.0456 秒(值会因机器而异)

4.2 场景 2:接口权限校验

在 Web 开发中(如 Flask/Django),用于校验用户是否登录,未登录则拒绝访问:

# 模拟用户登录状态(实际项目中会从Session/Token中获取)
current_user = {"is_login": False, "username": "guest"}def login_required(func):"""校验用户是否登录的装饰器"""def wrapper(*args, **kwargs):if not current_user["is_login"]:# 未登录,返回错误信息return "Error:请先登录!"# 已登录,执行原函数(比如返回用户中心页面)return func(*args, **kwargs)return wrapper# 测试:需要登录才能访问的接口
@login_required
def user_center():return f"欢迎 {current_user['username']} 进入用户中心!"# 未登录时调用
print(user_center())  # 输出:Error:请先登录!# 模拟登录后再调用
current_user["is_login"] = True
current_user["username"] = "pythoner"
print(user_center())  # 输出:欢迎 pythoner 进入用户中心!

4.3 场景 3:函数调用缓存

对于计算密集型函数(如斐波那契数列、大数字运算),用装饰器缓存已计算的结果,避免重复计算,提升性能:

def cache_decorator(func):"""缓存函数调用结果的装饰器(基于字典)"""cache = {}  # 用于存储缓存结果,key是函数参数,value是返回值def wrapper(*args):# 如果参数在缓存中,直接返回缓存结果if args in cache:print(f"命中缓存:{func.__name__}{args}")return cache[args]# 未命中缓存,执行原函数并缓存结果result = func(*args)cache[args] = resultprint(f"缓存新增:{func.__name__}{args} = {result}")return resultreturn wrapper# 测试:计算斐波那契数列(递归实现,未缓存时性能极差)
@cache_decorator
def fibonacci(n):if n <= 1:return nreturn fibonacci(n-1) + fibonacci(n-2)# 第一次调用:计算并缓存
print(fibonacci(5))
# 第二次调用:直接命中缓存
print(fibonacci(5))

执行结果如下,可见第二次调用直接复用了缓存:

缓存新增:fibonacci(0) = 0
缓存新增:fibonacci(1) = 1
缓存新增:fibonacci(2) = 1
缓存新增:fibonacci(3) = 2
缓存新增:fibonacci(4) = 3
缓存新增:fibonacci(5) = 5
5
命中缓存:fibonacci(5)
5

注意:Python 标准库 functools 中已经内置了更强大的缓存装饰器 lru_cache,实际项目中推荐直接使用:

from functools import lru_cache@lru_cache(maxsize=None)  # maxsize=None 表示不限制缓存大小
def fibonacci(n):if n <= 1:return nreturn fibonacci(n-1) + fibonacci(n-2)

五、注意事项:避免装饰器的 “坑”

5.1 问题 1:原函数的元信息丢失

装饰器会用 wrapper 函数替换原函数,导致原函数的元信息(如 __name____doc__)丢失。比如:

@log_decorator
def add(a, b):"""计算两数之和"""return a + bprint(add.__name__)  # 输出:wrapper(而非期望的 add)
print(add.__doc__)   # 输出:None(而非期望的“计算两数之和”)

5.2 解决:使用 functools.wraps

functools.wraps 是 Python 提供的工具,用于将原函数的元信息复制到 wrapper 函数中,解决元信息丢失问题。修改装饰器如下:

from functools import wrapsdef log_decorator(func):# 关键:用 wraps 装饰 wrapper,传入原函数 func@wraps(func)def wrapper(*args, **kwargs):print(f"调用函数:{func.__name__}")return func(*args, **kwargs)return wrapper# 再次测试
@log_decorator
def add(a, b):"""计算两数之和"""return a + bprint(add.__name__)  # 输出:add(正确)
print(add.__doc__)   # 输出:计算两数之和(正确)

5.3 问题 2:装饰器的执行顺序

当一个函数被多个装饰器修饰时,装饰器的执行顺序是 “从下到上包装,从上到下执行”。我们通过“计时装饰器和日志装饰器”的实战案例,直观感受这一规律。

实战案例:双装饰器(计时+日志)

首先,确保我们有两个功能明确的装饰器(已补充@wraps保留元信息):

import time
from functools import wraps# 1. 日志装饰器:打印函数调用和返回日志
def log_decorator(func):@wraps(func)def wrapper(*args, **kwargs):print(f"【日志装饰器】开始调用函数:{func.__name__}")result = func(*args, **kwargs)print(f"【日志装饰器】函数 {func.__name__} 调用结束,返回值:{result}")return resultreturn wrapper# 2. 计时装饰器:统计函数执行时间
def time_decorator(func):@wraps(func)def wrapper(*args, **kwargs):print(f"【计时装饰器】开始计时,函数:{func.__name__}")start_time = time.time()result = func(*args, **kwargs)end_time = time.time()print(f"【计时装饰器】计时结束,函数 {func.__name__} 执行时间:{end_time - start_time:.4f} 秒")return resultreturn wrapper

接着,用两个装饰器修饰同一个原函数(注意装饰器的书写顺序):

# 原函数:模拟一个简单的业务逻辑(休眠0.5秒,返回两数之和)
@time_decorator  # 装饰器1:写在上方
@log_decorator   # 装饰器2:写在下方(靠近原函数)
def calculate_sum(a, b):time.sleep(0.5)  # 模拟业务耗时return a + b# 调用函数,观察执行顺序
print("最终结果:", calculate_sum(3, 5))

执行结果与分析​

运行上述代码,输出如下:

【计时装饰器】开始计时,函数:calculate_sum
【日志装饰器】开始调用函数:calculate_sum
【日志装饰器】函数 calculate_sum 调用结束,返回值:8
【计时装饰器】计时结束,函数 calculate_sum 执行时间:0.5012 秒
最终结果: 8

结合结果,我们拆解 “包装顺序” 和 “执行顺序”:​

  1. 包装顺序(从下到上):​
    原函数 calculate_sum 先被下方的 log_decorator 包装(形成 log_wrapper),再被上方的 time_decorator 包装(形成 time_wrapper)。最终调用的 calculate_sum,实际是 time_wrapper(log_wrapper(calculate_sum))。​

  2. 执行顺序(从上到下):​
    调用函数时,先进入上方装饰器 time_decorator 的逻辑(打印 “开始计时”),再进入下方装饰器 log_decorator 的逻辑(打印 “开始调用”),接着执行原函数,最后按 “日志装饰器→计时装饰器” 的顺序收尾。​

通过这个案例可以明确:装饰器写在上方的,先执行逻辑;写在下方的,后执行逻辑。

六、总结

装饰器是 Python 中极具优雅性和实用性的特性,核心是 “不修改原函数代码,为函数添加额外功能”。掌握它需要记住以下 3 点:

  1. 本质:装饰器是 “接收函数、返回函数” 的函数,基于 “函数是一等公民” 实现;

  2. 用法:用 @装饰器名 语法糖应用装饰器,带参数的装饰器需多一层嵌套;

  3. 实战:常用场景包括日志、计时、权限校验、缓存,记得用 functools.wraps 保留原函数元信息。

http://www.dtcms.com/a/504564.html

相关文章:

  • 保定网站建设制作开发平台网站服务器搭建XP
  • 18-机器学习与大模型开发数学教程-第1章 1-10 本章总结与习题
  • Qt如何翻译JSON内容
  • 设计师的网站临沂天元建设集团
  • 做音乐网站的目的和意义新开网站seo
  • cnzz统计代码放在后台网站为什么没显示外贸响应式网站设计
  • 组合数学 第四章 生成排列与组合
  • Nginx 1.29.2源码通过Windows进行跨平台编译
  • joomla网站建设互联网平台推广方案
  • 建设网站的一般过程视频网站用什么cms
  • C++:jieba库的安装使用保姆级教程
  • 网站空间的根目录刷粉网站推广便宜
  • 建设网站需要哪些流程图办公室装饰
  • 校园官方网站建设营销软文范文200字
  • 找网络公司做网站需要注意的青岛建设银行股份有限公司网站
  • 苏州设置网站建设怎么看网站是哪家公司做的
  • 使用ACME自动签发SSL 证书
  • 泉州网站建设技术公司成功的网络营销案例及分析
  • 网站数据库连接错误建设项目所在地公共媒体网站
  • 网站如何提高转化率社区网站设计
  • 建站优化公司黎平网站开发
  • 免费php企业网站源码外贸网站建设厦门
  • 个人做的网站能备案吗帝国cms 网站名称
  • 广州智能模板建站大型公司办公室设计
  • 做网站需要什么基础主体备案与网站备案
  • 惠东网站建设网站制作用的软件有哪些
  • 我想网上做网站搜索引擎实训心得体会
  • 猪八戒网站怎么做任务wordpress位置
  • 网站建设unohacha电子招标投标平台网站建设
  • 夹娃娃网站如何做网站开发struts