嘿,各位编程界的弄潮儿,今天咱们聊点有意思的——Python 装饰器!别听到“装饰器”三个字就觉得高深莫测,它其实就像给你的函数穿件“定制款外套”,让它在不改变自身结构的情况下,拥有更多技能。
开场白:函数,你的衣服呢?
想象一下,你是一个函数,每天兢兢业业地完成任务。突然有一天,老板跟你说:“小伙子,你很棒,但是现在我们需要你每次工作前先打个卡,工作后写个报告。” 你的第一反应肯定是:“啥?还要改我的代码?这么麻烦!”
这时候,装饰器就闪亮登场了。它就像一个裁缝,不用动你的“函数本体”,就能给你缝制一件“外套”,让你自动完成打卡和写报告的任务。
第一部分:最简单的装饰器:给函数加个“小尾巴”
咱们先从最简单的装饰器开始,看看它是怎么工作的。
def say_hello():
print("Hello!")
say_hello() # 输出: Hello!
# 定义一个简单的装饰器
def my_decorator(func):
def wrapper():
print("Before calling the function.")
func()
print("After calling the function.")
return wrapper
# 使用装饰器
@my_decorator
def say_hello():
print("Hello!")
say_hello() # 输出:
# Before calling the function.
# Hello!
# After calling the function.
这段代码里,my_decorator
就是一个装饰器。它接收一个函数 func
作为参数,返回一个新的函数 wrapper
。wrapper
函数在执行 func
之前和之后,都加上了一些额外的操作(打印信息)。
@my_decorator
这一行代码,就是“语法糖”,它等价于:
say_hello = my_decorator(say_hello)
也就是说,say_hello
现在指向了 wrapper
函数。 当你调用 say_hello()
时,实际上是在执行 wrapper()
函数。
总结一下:
- 装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数。
@decorator_name
语法糖,简化了装饰器的使用。
第二部分:让装饰器“聪明”一点:带参数的函数怎么办?
如果被装饰的函数需要接收参数,那我们的装饰器也需要“聪明”一点,能够处理这些参数。
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before calling the function.")
result = func(*args, **kwargs)
print("After calling the function.")
return result
return wrapper
@my_decorator
def greet(name):
print(f"Hello, {name}!")
return f"Greeting: Hello, {name}!"
greeting = greet("Alice") # 输出:
# Before calling the function.
# Hello, Alice!
# After calling the function.
print(greeting) # 输出: Greeting: Hello, Alice!
这里,wrapper
函数使用了 *args
和 **kwargs
来接收任意数量的位置参数和关键字参数,并将它们传递给被装饰的函数 func
。这样,无论 greet
函数接收什么参数,my_decorator
都能正确地处理。
重点:
*args
用于接收任意数量的位置参数。**kwargs
用于接收任意数量的关键字参数。- 务必将
func
的返回值返回,否则被装饰的函数可能无法正常工作。
第三部分:给装饰器加点“个性”:带参数的装饰器
有时候,我们希望装饰器的行为能够根据一些参数进行调整。例如,我们可能希望控制在函数执行前后打印信息的格式。这时候,我们就需要带参数的装饰器。
def log_decorator(log_message):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"Log: {log_message} - Before calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Log: {log_message} - After calling {func.__name__}")
return result
return wrapper
return decorator
@log_decorator("Important")
def calculate(x, y):
print(f"Calculating {x} + {y}")
return x + y
result = calculate(5, 3) # 输出:
# Log: Important - Before calling calculate
# Calculating 5 + 3
# Log: Important - After calling calculate
print(result) # 输出: 8
这个例子中,log_decorator
接收一个参数 log_message
,然后返回一个真正的装饰器 decorator
。decorator
接收被装饰的函数 func
,并返回 wrapper
函数。
分析:
@log_decorator("Important")
实际上是先调用log_decorator("Important")
,返回decorator
函数。- 然后,
@decorator
作用于calculate
函数。
精髓: 带参数的装饰器实际上是两层函数嵌套。外层函数接收装饰器的参数,内层函数才是真正的装饰器。
第四部分:让装饰器“记住”点什么:带状态的装饰器
有时候,我们希望装饰器能够“记住”一些信息,例如函数被调用的次数。 这时候,我们就需要带状态的装饰器。
def count_calls(func):
def wrapper(*args, **kwargs):
wrapper.call_count += 1
print(f"Function {func.__name__} called {wrapper.call_count} times")
return func(*args, **kwargs)
wrapper.call_count = 0 # 初始化调用次数
return wrapper
@count_calls
def my_function():
print("Executing my_function")
my_function() # 输出:
# Function my_function called 1 times
# Executing my_function
my_function() # 输出:
# Function my_function called 2 times
# Executing my_function
这个例子中,我们在 wrapper
函数中添加了一个 call_count
属性,用于记录函数被调用的次数。每次调用 wrapper
函数时,call_count
都会加 1。
关键:
- 在
wrapper
函数外部初始化call_count
属性。 - 使用
wrapper.call_count
来访问和修改这个属性。
第五部分:更优雅的带状态装饰器:使用类
使用类来实现带状态的装饰器,代码会更清晰、更易于维护。
class CallCounter:
def __init__(self, func):
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"Function {self.func.__name__} called {self.call_count} times")
return self.func(*args, **kwargs)
@CallCounter
def my_function():
print("Executing my_function")
my_function() # 输出:
# Function my_function called 1 times
# Executing my_function
my_function() # 输出:
# Function my_function called 2 times
# Executing my_function
这个例子中,CallCounter
类实现了 __init__
和 __call__
方法。
__init__
方法接收被装饰的函数func
,并初始化call_count
属性。__call__
方法让类的实例可以像函数一样被调用。 每次调用实例时,call_count
都会加 1。
优势:
- 代码结构更清晰,更容易理解和维护。
- 可以使用类的属性和方法来管理装饰器的状态。
第六部分:装饰器的应用场景
装饰器在实际开发中有很多应用场景,例如:
- 日志记录: 记录函数的调用信息,方便调试和问题排查。
- 性能测试: 测量函数的执行时间,优化代码性能。
- 缓存: 缓存函数的计算结果,避免重复计算。
- 权限验证: 验证用户的权限,控制对函数的访问。
- 重试机制: 在函数执行失败时自动重试。
- 类型检查: 检查函数的参数类型,提高代码的健壮性。
举例:权限验证
def requires_permission(permission):
def decorator(func):
def wrapper(*args, **kwargs):
user = kwargs.get('user') # 假设函数接收一个 user 参数
if user and user.has_permission(permission):
return func(*args, **kwargs)
else:
print(f"User does not have permission: {permission}")
return None # 或者抛出异常
return wrapper
return decorator
class User:
def __init__(self, name, permissions):
self.name = name
self.permissions = permissions
def has_permission(self, permission):
return permission in self.permissions
@requires_permission("edit")
def edit_document(document_id, user):
print(f"User {user.name} is editing document {document_id}")
return f"Document {document_id} edited by {user.name}"
user1 = User("Alice", ["view", "edit"])
user2 = User("Bob", ["view"])
edit_document(123, user=user1) # 输出: User Alice is editing document 123
edit_document(456, user=user2) # 输出: User does not have permission: edit
第七部分:装饰器的注意事项
- 避免循环引用: 装饰器可能会导致循环引用,从而引发内存泄漏。
- 装饰器的顺序: 多个装饰器的顺序很重要,不同的顺序可能会导致不同的结果。
- 保留函数元信息: 装饰器会修改函数的元信息 (例如
__name__
,__doc__
),可以使用functools.wraps
来保留原始函数的元信息。
import functools
def my_decorator(func):
@functools.wraps(func) # 保留func的元信息
def wrapper(*args, **kwargs):
"""Wrapper function docstring"""
print("Before calling the function.")
result = func(*args, **kwargs)
print("After calling the function.")
return result
return wrapper
@my_decorator
def say_hello():
"""Original function docstring"""
print("Hello!")
print(say_hello.__name__) # 输出: say_hello
print(say_hello.__doc__) # 输出: Original function docstring
第八部分:装饰器设计模式总结
特性 | 简单装饰器 | 带参数的装饰器 | 带状态的装饰器 | 基于类的装饰器 |
---|---|---|---|---|
结构 | 单层函数嵌套 | 双层函数嵌套 | 单层或多层函数嵌套,依赖于外部变量 | 定义一个类,实现 __init__ 和 __call__ 方法 |
功能 | 在函数执行前后添加额外的操作 | 根据参数调整装饰器的行为 | 记录和维护装饰器的状态,例如函数调用次数 | 代码结构更清晰,更容易管理状态 |
应用场景 | 日志记录、性能测试 (最简单的场景) | 灵活配置日志格式、权限验证 | 缓存、重试机制 | 复杂的权限管理、自定义行为控制 |
优点 | 简单易懂 | 灵活性高 | 可以实现更复杂的功能 | 面向对象的设计,代码可读性高 |
缺点 | 功能有限 | 理解起来稍微复杂 | 需要注意状态的维护 | 稍微复杂,需要理解类的概念 |
关键点 | 理解函数作为一等公民的概念 | 理解闭包的概念 | 在 wrapper 函数外部初始化状态变量 |
实现 __call__ 方法,让类的实例可以像函数一样被调用 |
结束语:装饰器,你的编程瑞士军刀
装饰器是 Python 中一个非常强大的特性,它可以让你在不修改原有代码的情况下,扩展函数的功能。 掌握装饰器,你就能写出更简洁、更优雅、更易于维护的代码。所以,赶紧拿起你的键盘,开始尝试使用装饰器吧!相信我,你会爱上它的! 记住,编程就像生活,需要不断地装饰,才能变得更加精彩!
下次见,各位!