Python装饰器(Decorator)是一种设计模式,允许你在不修改函数代码的前提下,增强或修改函数的功能。装饰器本质上是一个高阶函数,它接收一个函数作为参数,并返回一个增强后的函数。下面我们详细介绍Python装饰器的概念、使用场景及实现方法。
一、基本概念
装饰器可以理解为一种特殊的函数,它用于在函数执行前后添加额外的功能。装饰器主要分为两种:函数装饰器和类装饰器。装饰器的核心思想是通过闭包(closure)来实现。
装饰器的使用符合了面向对象编程的开放封闭原则。开放封闭原则主要体现在两个方面:
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。
1.1 闭包(Closure)
闭包是指在一个内嵌函数中引用了其外层函数的变量,然后返回这个内嵌函数的情况。闭包能够记住并访问其定义所在的作用域,即使这个作用域已经被销毁。
def outer_func(msg):
def inner_func():
print(msg)
return inner_func
closure_func = outer_func("Hello, World!")
closure_func()
# 输出: Hello, World!
1.2 装饰器基本结构
装饰器通常包括一个嵌套函数,在嵌套函数中调用被装饰的函数,并在调用前后添加额外的操作。
def decorator_func(func):
def wrapper_func():
print("Function is being called")
func()
print("Function has been called")
return wrapper_func
@decorator_func
def say_hello():
print("Hello!")
say_hello()
#输出结果
Function is being called
Hello!
Function has been called
'@decorotor'方式有地方称之为python语法糖,必须紧挨需要装饰的函数或类,对于多个装饰器需要由上到下书写到需要装饰的函数或类定义处。
二、应用场景
装饰器在实际开发中有广泛的应用,常用于日志记录、性能测试、事务处理、权限校验等场景。
2.1 日志记录
通过装饰器,可以在函数调用前后自动记录日志信息。
import logging
def log_decorator(func):
def wrapper(*args, **kwargs):
logging.info(f"Calling function {func.__name__}")
result = func(*args, **kwargs)
logging.info(f"Function {func.__name__} finished")
return result
return wrapper
@log_decorator
def process_data(data):
print(f"Processing {data}")
process_data("sample data")
2.2 计算函数执行时间
装饰器可以用来测量函数的执行时间,帮助优化代码性能。
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Function {func.__name__} took {end_time - start_time} seconds")
return result
return wrapper
@timer_decorator
def slow_function():
time.sleep(2)
slow_function()
2.3 权限验证
装饰器可以用于检查用户是否具有执行某些操作的权限。
def permission_required(permission):
def decorator(func):
def wrapper(*args, **kwargs):
user = kwargs.get('user')
if user and user.has_permission(permission):
return func(*args, **kwargs)
else:
raise PermissionError(f"User does not have {permission} permission")
return wrapper
return decorator
@permission_required('admin')
def delete_user(user, user_id):
print(f"User {user_id} deleted")
class User:
def __init__(self, permissions):
self.permissions = permissions
def has_permission(self, permission):
return permission in self.permissions
admin_user = User(['admin'])
delete_user(user=admin_user, user_id=123)
三、装饰器类型
3.1 简易装饰器
上面提到的装饰器大多为简易装饰器,即被装饰函数及装饰器均不带任何参数。
def decorator_func(func):
def wrapper_func():
print("Function is being called")
func()
print("Function has been called")
return wrapper_func
@decorator_func
def say_hello():
print("Hello!")
3.2 带参函数装饰器
装饰器并不会影响带有参数函数,对于带有参数的函数需要在装饰器中再嵌套一层。
import time
def count_time(func):
def wrapper(*args,**kwargs):
t1 = time.time()
func(*args,**kwargs)
print("执行时间为:", time.time() - t1)
return wrapper
@count_time
def say_hi(msg = 'hello word'):
time.sleep(1)
print(msg)
say_hi()
wrapper函数的参数为*args和**kwargs,表示可以接受任意参数
3.3 带参装饰器
装饰器本身可以接受参数,这需要再嵌套一层函数。
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi!")
say_hi()
3.4 多层装饰器
多个装饰器可以叠加使用,执行顺序是从下到上。
def decorator1(func):
def wrapper():
print("Decorator 1")
func()
return wrapper
def decorator2(func):
def wrapper():
print("Decorator 2")
func()
return wrapper
@decorator1
@decorator2
def greet():
print("Hello!")
greet()
#输出
Decorator 1
Decorator 2
Hello!
3.5 类装饰器
在python中,其实也可以同类来实现装饰器的功能称之为类装饰器。类装饰器的实现是调用了类里面的__call__函数。类装饰器的写法比我们装饰器函数的写法更加简单。
当我们将类作为一个装饰器,工作流程:
通过__init__()方法初始化类
通过__call__()方法调用真正的装饰方法
class ClassDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("ClassDecorator is being called")
return self.func(*args, **kwargs)
@ClassDecorator
def hello():
print("Hello from class decorator")
hello()
四、装饰器拓展
4.1 名称修复
装饰器通常会将一个函数替换成另一个函数,修改函数的属性,或者在函数周围添加一些代码来实现某些功能,这可能会导致函数元数据信息的丢失,比如函数名、文档字符串、参数列表等等,为了避免这种情况,可以使用 wraps 装饰器来修饰装饰器本身,从而确保被修饰的函数保留了其原有的元数据信息不变。
wraps 装饰器本身也是一个装饰器,它接受一个函数作为参数,并返回一个包装函数。wraps 装饰器会将包装函数的名称、文档字符串、参数列表等元数据信息更新为原函数的元数据信息,从而确保被修饰的函数可以正确地保留原有的元数据信息。
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Before the function is called.')
result = func(*args, **kwargs)
print('After the function is called.')
return result
return wrapper
@my_decorator
def say_hello(name):
"""A function that says hello"""
print(f'Hello, {name}!')
print(say_hello.__name__) # 输出 'say_hello'
print(say_hello.__doc__) # 输出 'A function that says hello'
尤其在pytest类似测试框架中测试程序自动识别带有'test_'前缀的测试函数,如果不使用名称修复很有可能导致测试函数无法被识别情况。
4.2 局域变量
对于多层装饰器且参数遍历场景,如果在使用set_random_seed0或set_random_seed1都没有特意设定种子,则每次运行种子情况使用2个装饰器的结果是不一样的,对于set_random_seed0参数遍历过程中每次产生的种子都是不一样的,对于set_random_seed1参数遍历过程中每次产生的种子都是一样的即只在第一次运行会有随机种子产生。
import functools
def set_random_seed0(seed=None):
def decorator(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
local_seed = seed
if local_seed is None:
local_seed = np.random.randint(2**31)
/*具体代码*/
print(f'\nseed-{local_seed}',end=' ')
return func(*args, **kwargs)
return wrapper
return decorator
def set_random_seed1(seed=None):
def decorator(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
nonlocal seed
if seed is None:
seed = np.random.randint(2**31)
/*具体代码*/
print(f'\nseed-{seed}',end=' ')
return func(*args, **kwargs)
return wrapper
return decorator