装饰器是Python中的一种高级功能,允许您修改或增强函数或类的行为,而无需直接修改其源代码。
基本概念:
装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。
基本语法:
python">@decorator_function
def target_function():pass
等同于:
python">def target_function():pass
target_function = decorator_function(target_function)
python">def simple_decorator(func):def wrapper():print("函数开始执行...")func()print("函数执行完成!")return wrapper@simple_decorator
def hello():print("hello world")hello()输出:
函数开始执行...
hello world
函数执行完成!
解析:simple_decorator 就是一个装饰器。它接受一个函数 func 作为参数,并返回一个新的函数 wrapper。当使用 @simple_decorator 语法糖在 hello 函数上应用装饰器时,实际上是调用了 simple_decorator(hello),返回了 wrapper 函数,然后当你执行 hello() 时,实际上是执行了 wrapper()。
2、带参数的函数装饰器
装饰器可以通过在 wrapper 函数中使用 *args 和 **kwargs 来处理这个问题。
python">def decorator_with_args(func):def wrapper(*args, **kwargs):print("参数:", args, kwargs)func(*args, **kwargs)return wrapper@decorator_with_args
def greet(name, message="Hello"):print(f"{message}, {name}")greet("Python")
当原始函数(如 greet)需要接受参数时,装饰器内的 wrapper 函数需要能够接收任意数量的位置参数 (*args) 和关键字参数 (**kwargs)。
• *args 和 **kwargs :这两个特殊的参数用于让 wrapper 函数能接受任意数量和类型的参数,从而使装饰器更加通用。*args 用于未命名的参数(位置参数),而 **kwargs 用于命名参数(关键字参数)。
• func(*args, **kwargs) 调用 :这确保了无论原始 greet 函数需要哪些参数,wrapper 都能传递正确的参数给它。
如下:
3、装饰器带参数
有时候你可能希望装饰器本身能接受一些参数。为了实现这个功能,你需要再加一层包装。
python">def repeat_decorator(times):def decorator(func):def wrapper(*args, **kwargs):for _ in range(times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator@repeat_decorator(3)
def greet(name):print(f"Hello, {name}!")greet("Alice") # 将打印 3 次 "Hello, Alice!"
这个例子展示了如何创建一个带参数的装饰器。装饰器 repeat_decorator接受一个参数 times=3,表示需要重复执行原始函数 func 的次数。
• 外层函数 repeat_decorator(times):这是装饰器的外层,负责接受装饰器的参数(在这里是 times),并返回一个装饰器函数 decorator。
• 中层函数 decorator(func):这是实际的装饰器函数,负责接收目标函数 func 并返回 wrapper 函数。
• 内层函数 wrapper(*args, **kwargs):这是包裹目标函数 func 的函数,它通过 *args 和 **kwargs 接受任意数量和类型的参数,并根据外层 repeat_decorator函数的参数 times 决定执行 func。
二、装饰器的高级用法
1. 类作为装饰器
类装饰器利用了Python的 call 方法,使得类的实例可以像函数一样被调用;
Python 中的装饰器不仅可以是函数,也可以是类。类装饰器主要依赖于 call 方法,当一个类的实例被当作函数调用时,call 方法就会被执行。
python">class CountCalls:def __init__(self, func):self.func = funcself.num_calls = 0def __call__(self, *args, **kwargs):self.num_calls += 1print(f"调用 {self.func.__name__} {self.num_calls} 次")return self.func(*args, **kwargs)@CountCalls
def say_hello():print("Hello!")say_hello()
say_hello()
2、保留原函数的元数据
使用装饰器时,一个常见的问题是被装饰的函数其实是被替换成了一个新的函数(即 wrapper)。这可能会导致原函数的元数据丢失,比如函数的名称和文档字符串。为了解决这个问题,Python 的 functools 模块提供了一个 wraps 装饰器。
使用 functools.wraps 装饰器来保留被装饰函数的元数据:
python">from functools import wrapsdef my_decorator(func):@wraps(func)def wrapper(*args, **kwargs):"""Wrapper function"""print("Something is happening before the function is called.")return func(*args, **kwargs)return wrapper@my_decorator
def say_whee():"""Say whee!"""print("Whee!")print(say_whee.__name__) # 输出 'say_whee'
print(say_whee.__doc__) # 输出 'Say whee!'
三、装饰器的常见应用
日志记录、性能测量、访问控制和认证、缓存、错误处理和重试逻辑
四、注意事项
1、装饰器在函数定义时就会执行,而不是在函数调用时。
2、多个装饰器可以堆叠使用,执行顺序是从下到上。
当多个装饰器应用到一个函数上时,它们的执行顺序是从下到上的:
python">@decorator1
@decorator2
def func():pass
这等同于:
python">func = decorator1(decorator2(func))
3、装饰器可能会影响函数的性能,特别是在频繁调用的情况下。
4、使用 functools.wraps 可以保留被装饰函数的元数据。