侧边栏壁纸
博主头像
路小飞博主等级

行动起来,活在当下

  • 累计撰写 72 篇文章
  • 累计创建 12 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

2.5 迭代器&生成器&&装饰器

路小飞
2025-07-22 / 0 评论 / 0 点赞 / 1 阅读 / 5398 字

2.5.1 迭代器

2.5.2 生成器

2.5.3 装饰器

在编程中,我们经常需要在不修改原函数核心逻辑的前提下,为其增加额外功能(如日志记录、时间统计等)。Python 的装饰器(Decorator)就是解决这类问题的优雅方案。我们通过一个“员工打卡”的场景,逐步理解装饰器的设计思路和使用方法。

场景:为打卡功能添加时间记录
产品原型:打卡功能

假设我们有一个简单的打卡函数,用于输出员工的打卡信息:

def punch():
    print('昵称:路小飞, 部门:摸鱼事业部, 上班打卡成功!')

if __name__ == "__main__":
    punch()

输出结果:

昵称:路小飞, 部门:摸鱼事业部, 上班打卡成功!

产品需求:希望在打卡信息前增加当前日期和时间(如 2025-07-21 23:16:21)。

初级方案:直接修改原函数

最直接的想法是在 punch 函数中添加时间打印逻辑:

import time

def punch2():
    # 新增:打印当前时间
    print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
    print('昵称:路小飞, 部门:摸鱼事业部, 上班打卡成功!')

if __name__ == "__main__":
    punch2()

输出结果:

2025-07-21 23:16:21
昵称:路小飞, 部门:摸鱼事业部, 上班打卡成功!

问题

这种方式直接修改了原函数的逻辑,违反了“开闭原则”(对修改封闭,对扩展开放)。如果其他场景需要调用不带时间的打卡功能,就必须重写一个新函数,导致代码冗余。

进阶方案:函数式编程(分离扩展逻辑)

利用 Python 中“函数是对象”的特性,我们可以将“添加时间”的逻辑抽离成一个独立函数,通过参数接收原函数并调用:

import time

def punch():
    print('昵称:路小飞, 部门:摸鱼事业部, 上班打卡成功!')

# 独立的“添加时间”函数
def add_time(func):
    print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
    func()  # 调用原函数

if __name__ == "__main__":
    add_time(punch)  # 将punch作为参数传入

输出结果与之前一致,但优势在于:

  • 原函数 punch 的逻辑未被修改;

  • 时间记录功能 add_time 可复用(如给其他函数添加时间记录):

    def holiday():
        print('天气太冷,今天放假')
    
    # 给holiday函数也添加时间记录
    add_time(holiday)
    

    输出:

    2025-07-21 23:28:33
    天气太冷,今天放假
    

问题
每次调用原函数时,都必须手动传入 add_time 函数,不够简洁。如果需要长期为 punch 函数绑定时间记录功能,这种方式不够高效。

最佳方案:装饰器(自动绑定扩展逻辑)

装饰器的核心思想是“动态为函数添加功能”,且无需修改原函数调用方式。它通过以下三步实现:

  1. 接收一个函数作为参数;
  2. 定义嵌套的“包装函数”,在包装函数中实现扩展功能(如打印时间)并调用原函数;
  3. 返回包装函数,替代原函数。

用装饰器改写上述代码:

import time

# 定义装饰器:接收原函数,返回包装函数
def datatime_decorator(func):
    def datatime_wrapper():  # 包装函数:包含扩展逻辑+原函数调用
        print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
        func()  # 调用原函数
    return datatime_wrapper  # 返回包装函数

# 使用装饰器:为punch函数绑定时间记录功能
def punch():
    print('昵称:路小飞, 部门:摸鱼事业部, 上班打卡成功!')

if __name__ == "__main__":
    # 将punch替换为装饰器返回的包装函数
    decorated_punch = datatime_decorator(punch)
    decorated_punch()  # 调用包装函数(自动执行时间记录+原函数逻辑)

优势

  • 原函数 punch 完全未修改;
  • 扩展逻辑与原函数解耦,可灵活绑定或解绑。
简化使用:装饰器的“语法糖”

上述代码虽然实现了功能,但调用时需要手动绑定装饰器,略显繁琐。Python 提供了 @ 语法糖,可简化装饰器的使用:

import time

def datatime_decorator(func):
    def datatime_wrapper():
        print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
        func()
    return datatime_wrapper

# 用@语法糖绑定装饰器,等价于 punch = datatime_decorator(punch)
@datatime_decorator
def punch():
    print('昵称:路小飞, 部门:摸鱼事业部, 上班打卡成功!')

if __name__ == "__main__":
    punch()  # 直接调用原函数名,实际执行的是包装函数

效果
调用 punch() 时,会自动执行时间记录+原打卡逻辑,输出与之前一致。这就是装饰器的便捷之处——用简洁的语法实现了功能扩展。

兼容带参数的函数

如果原函数需要接收参数(如动态指定员工姓名、部门),装饰器需要支持参数传递。通过 *args(接收位置参数)和 **kwargs(接收关键字参数)可实现通用兼容:

import time

def datatime_decorator(func):
    # 包装函数支持任意参数
    def datatime_wrapper(*args, **kwargs):
        print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
        func(*args, **kwargs)  # 将参数传递给原函数
    return datatime_wrapper

@datatime_decorator
def punch(name, department, action="打卡"):  # 带参数的原函数
    print(f'昵称:{name}  部门:{department} {action}成功')

if __name__ == '__main__':
    # 支持多种调用方式
    punch('路小飞', '摸鱼事业部', '上班打卡')  # 位置参数
    print('-' * 30)
    punch(name='张三', department='技术部', action='下班打卡')  # 关键字参数
    print('-' * 30)
    punch('李四', '销售部')  # 混合参数(使用默认action)

输出结果:

2025-07-22 00:11:00
昵称:路小飞  部门:摸鱼事业部 上班打卡成功
------------------------------
2025-07-22 00:11:00
昵称:张三  部门:技术部 下班打卡成功
------------------------------
2025-07-22 00:11:00
昵称:李四  部门:销售部 打卡成功
总结

装饰器是一种“在不修改原函数的前提下为其动态添加功能”的编程模式,核心优势在于:

  1. 遵循开闭原则,降低代码耦合度;
  2. 扩展功能可复用(如日志、缓存、权限校验等);
  3. Python 的 @ 语法糖使其使用极为简洁。

理解装饰器的关键是掌握“函数是对象”的特性——通过嵌套函数封装扩展逻辑,并返回新函数替代原函数,从而实现灵活的功能扩展。

0

评论区