装饰器 (Decorators)
装饰器是 Python 中一个非常强大且常用的功能,它允许我们在不修改原函数代码的情况下,动态地增加函数的功能。本质上,装饰器是一个接收函数作为参数并返回一个新函数的高阶函数。
1. 什么是装饰器?
假设我们有一个函数,我们想在它执行前后打印日志,但不想修改函数本身的逻辑。
不使用装饰器
def my_func():
print("Executing my_func")
def log_wrapper(func):
print("Before calling...")
func()
print("After calling...")
log_wrapper(my_func)
使用装饰器
Python 提供了 @ 语法糖,使上面的逻辑更加简洁。
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# 输出:
# Something is happening before the function is called.
# Hello!
# Something is happening after the function is called.
2. 装饰带参数的函数
如果你要装饰的函数接收参数,那么装饰器内部的 wrapper 函数也必须能够接收这些参数。最通用的做法是使用 *args 和 **kwargs。
def log_args(func):
def wrapper(*args, **kwargs):
print(f"Calling function with args: {args}, kwargs: {kwargs}")
return func(*args, **kwargs) # 记得返回原函数的执行结果
return wrapper
@log_args
def add(a, b):
return a + b
result = add(3, 5)
# 输出: Calling function with args: (3, 5), kwargs: {}
print(result) # 输出: 8
关于 *args 和 **kwargs
这两个是 Python 中的可变参数语法,在装饰器中非常关键:
*args(Arguments):用于接收任意数量的位置参数(Positional Arguments),并将它们打包成一个元组 (Tuple)。例如,调用add(1, 2)时,args就是(1, 2)。**kwargs(Keyword Arguments):用于接收任意数量的关键字参数(Keyword Arguments),并将它们打包成一个字典 (Dictionary)。例如,调用func(a=1, b=2)时,kwargs就是{'a': 1, 'b': 2}。
在 wrapper 函数中使用它们,可以确保无论原函数 func 定义了什么样的参数,wrapper 都能正确地接收并传递给它。
3. 带参数的装饰器
有时候,我们需要给装饰器本身传递参数,比如设置日志级别或重试次数。这时候需要多包一层函数。
def repeat(num_times):
def decorator_repeat(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result # 放在循环外,只返回最后一次调用的结果
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}")
greet("World")
# 输出:
# Hello World
# Hello World
# Hello World
如果原函数没有返回值怎么办?
这也是完全没问题的。在 Python 中,如果一个函数没有显式的 return 语句,它默认返回 None。
所以在上面的例子中,result 会接收到 None,并且 wrapper 最终也会返回 None。这样做的好处是它可以保持原函数的行为一致:如果原函数有返回值,装饰器就返回它;如果没有,就返回 None,就像原函数一样。
为什么需要三层函数?
这确实容易让人头晕,我们可以这样拆解:
- 第一层
repeat(num_times):这是最外层,它的任务是接收你传递给装饰器的参数(如3),并返回真正的装饰器函数(即第二层)。 - 第二层
decorator_repeat(func):这是真正的装饰器,它的任务是接收被装饰的函数(如greet),并返回包装后的函数(即第三层)。 - 第三层
wrapper(*args, **kwargs):这是最终执行的函数,它的任务是接收原函数的参数(如"World"),并在其中执行具体的逻辑(由于闭包特性,它可以同时访问num_times和func)。
简单来说:第一层收装饰器参数,第二层收函数,第三层收函数的参数。
4. functools.wraps 的重要性
使用装饰器后,原函数的元信息(如 __name__、__doc__)会被 wrapper 函数覆盖。为了保留原函数的元信息,应该使用 functools.wraps 装饰 wrapper 函数。
import functools
def my_decorator(func):
@functools.wraps(func) # 保留原函数的元数据
def wrapper(*args, **kwargs):
"""Wrapper function docstring"""
print("Calling decorated function")
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""Example function docstring"""
print("Called example function")
print(example.__name__) # 输出: example (如果没有 @wraps,会输出 wrapper)
print(example.__doc__) # 输出: Example function docstring
5. 类装饰器
装饰器不仅可以修饰函数,也可以修饰类。
def singleton(cls): # cls 这里接收的是被装饰的类本身
"""单例模式装饰器"""
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs) # 相当于调用 DatabaseConnection()
return instances[cls]
return get_instance
@singleton
class DatabaseConnection:
def __init__(self):
print("Database connected")
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # 输出: True
总结
- 装饰器是修改函数行为的一种优雅方式,遵循“开放封闭原则”。
- 使用
@decorator语法糖应用装饰器。 - 使用
*args和**kwargs处理参数。 - 使用
functools.wraps保留原函数元数据。 - 常见应用:日志记录、性能测试、权限校验、缓存(
@lru_cache)。
下一步
掌握了这些高级特性后,我们可以将目光转向更实战的领域:算法与数据结构。在刷 LeetCode 时,Python 提供了许多内置的高效数据结构,能让你的解题事半功倍。