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 函数绑定时间记录功能,这种方式不够高效。
最佳方案:装饰器(自动绑定扩展逻辑)
装饰器的核心思想是“动态为函数添加功能”,且无需修改原函数调用方式。它通过以下三步实现:
- 接收一个函数作为参数;
- 定义嵌套的“包装函数”,在包装函数中实现扩展功能(如打印时间)并调用原函数;
- 返回包装函数,替代原函数。
用装饰器改写上述代码:
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
昵称:李四 部门:销售部 打卡成功
总结
装饰器是一种“在不修改原函数的前提下为其动态添加功能”的编程模式,核心优势在于:
- 遵循开闭原则,降低代码耦合度;
- 扩展功能可复用(如日志、缓存、权限校验等);
- Python 的
@语法糖使其使用极为简洁。
理解装饰器的关键是掌握“函数是对象”的特性——通过嵌套函数封装扩展逻辑,并返回新函数替代原函数,从而实现灵活的功能扩展。
评论区