Python装饰器(一次搞清楚)
最重要的情绪管理是要明白,没有一种情绪是不应该的
一、简单装饰器
Python装饰器是一种语法糖,用于在不改变原有函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数,通常使用@
语法糖来应用装饰器。
1.装饰器本质是一个函数,可称之为函数装饰器;
2.装饰器也是一个闭包,即在非全局范围内定义的函数可以引用其外围空间中的变量;
3.装饰器以一个函数作为参数,并且返回值也是一个函数;
4.装饰器不能修改被装饰的函数代码;不能修改被装饰函数的调用方式;
下面是一个简单的装饰器示例:
def my_decorator(func):
def wrapper():
print("Before the function is called.")
res = func()
print("After the function is called.")
return res
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 调用被装饰的函数
say_hello()
在这个示例中,my_decorator是一个装饰器函数,它接收一个函数作为参数,并返回一个新的函数wrapper。wrapper函数包裹了原有的函数,它在调用原有函数之前和之后打印了额外的信息。@my_decorator
语法糖将say_hello函数传递给my_decorator,并将其返回的新函数wrapper赋值给say_hello,这样调用say_hello函数时,实际上是调用了wrapper函数。执行结果为:
二、装饰器的运用场景
装饰器的应用非常广泛,可以用于实现各种功能,例如:
- 记录函数执行时间:通过在装饰器函数中记录函数执行的开始和结束时间,可以计算函数的执行时间。
- 缓存函数结果:通过在装饰器函数中维护一个缓存,可以避免重复计算相同参数的函数结果,提高函数的执行效率。
- 实现权限控制:通过在装饰器函数中检查用户的权限,可以控制用户对某些函数的访问权限。
- 日志记录:通过在装饰器函数中记录函数的输入参数和返回值,可以方便地进行调试和错误排除。
- 错误处理:通过在装饰器函数中捕获异常并进行处理,可以避免函数抛出异常导致程序崩溃。
Python装饰器是一种非常强大的语法糖,可以帮助我们实现各种功能,提高代码的复用性和可维护性。
三、运用案列
案列一:Python缓存cache实现
Python中的缓存(cache)机制可以通过装饰器来实现,但并不是所有的缓存都是通过装饰器实现的。装饰器是一种常用的实现缓存的方式,但是Python中还有其他的缓存实现方式,例如使用字典、使用缓存库等。
使用装饰器实现缓存的原理是,在装饰器函数中维护一个字典,将函数的输入参数作为键,函数的返回值作为值,存储在字典中。在每次调用函数之前,先检查字典中是否已经存在相同输入参数的缓存结果,如果存在,则直接返回缓存结果,否则调用原函数计算结果,并将计算结果缓存到字典中。
下面是一个简单的装饰器缓存示例:
def cache(func):
cached_results = {}
def wrapper(*args):
if args in cached_results:
return cached_results[args]
result = func(*args)
cached_results[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
1.在这个示例中,cache是一个装饰器函数,它接收一个函数作为参数,并返回一个新的函数wrapper。wrapper函数维护了一个cached_results字典,用于存储函数的缓存结果。在调用被装饰的函数之前,wrapper函数先检查输入参数args是否已经存在于cached_results中,如果存在,则直接返回缓存结果,否则调用原函数计算结果,并将结果存储到cached_results中。
2.这个装饰器可以用于缓存计算复杂的函数,例如计算斐波那契数列的函数fibonacci。在第一次调用fibonacci(10)时,由于没有缓存结果,需要进行计算,计算结果存储到字典中。在后续的调用中,只需要从字典中获取结果即可,避免了重复计算,提高了程序的执行效率。
3.需要注意的是,装饰器缓存的实现方式可能存在一些问题,例如缓存数据的过期问题、内存占用问题等。因此,在实际使用中需要根据具体情况进行选择和调整,以确保程序的正确性和性能。
案列二:打印函数执行时间消耗
import time
# 注意:这个是带参数的装饰器
def calculate_execution_time(unit='s'):
def decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
if unit == 'ms':
execution_time *= 1000
print(f"函数 {func.__name__} 的执行时间为: {execution_time:.2f} 毫秒")
else:
print(f"函数 {func.__name__} 的执行时间为: {execution_time:.2f} 秒")
return result
return wrapper
return decorator
@calculate_execution_time(unit='ms')
def my_function(a, b):
# 假设这里是一个耗时的操作
time.sleep(1)
# 被装饰函数本身的功能
print("{0} + {1} = ".format(a, b), a+b)
my_function(1, 2)
1.上述代码定义了一个名为calculate_execution_time的装饰器,它接受一个可选的参数unit,用于指定执行时间的单位,默认为秒。装饰器内部定义了另一个函数decorator,它接受被装饰的函数func作为参数。在wrapper函数中,我们首先记录函数执行开始的时间戳start_time,然后调用被装饰的函数并获取其返回值result,最后计算函数执行所花费的时间,并根据unit参数选择合适的单位进行打印。
2.执行结果:
3.带参数的装饰器详见下文。
四、带参数的装饰器
Python装饰器可以用于装饰任何可调用对象,包括函数和类。当装饰函数带参数时,需要在装饰器函数里再定义一层函数来接收参数,这样才能将参数传递给被装饰的函数。
下面是一个简单的装饰器示例,演示了如何在装饰器函数中处理带参数的函数:
from functools import wraps
def repeat(num):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(num):
print(f"Running function {func.__name__} for the {i+1} time")
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("John")
1.在这个示例中,repeat是一个装饰器函数,它接收一个参数num,用于指定函数重复执行的次数。decorator是repeat的内部函数,它接收一个函数func作为参数,并返回一个新的函数wrapper。wrapper函数实现了函数的重复执行功能,它在循环中调用被装饰的函数func,并打印执行次数的信息。
另外,@wraps(func)
用于保存函数的元信息,例如函数名、参数、注释等,可以使用functools模块中的wraps装饰器来保存被装饰函数的原信息。
2.wraps装饰器实际上是一个装饰器工厂函数,它接收一个函数作为参数,并返回一个新的装饰器函数。这个新的装饰器函数会将被装饰的函数替换为自己,并使用functools模块中的update_wrapper函数来将被装饰函数的元信息复制到新的装饰器函数中。
3.如果不使用wraps装饰器来保存被装饰函数的元信息,那么被装饰函数的元信息会被覆盖,例如函数名会变成wrapper,函数注释会变成装饰器函数的注释。
4.在应用装饰器时,使用@
语法糖将装饰器函数repeat(3)
应用到函数greet上,例如:
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
这样,每次调用greet函数时,都会执行3次,打印出执行的次数和函数的输出结果。
5.需要注意的是,在装饰器函数中定义的参数,需要在装饰器的每一层函数中进行传递和处理。在这个示例中,num参数在repeat函数中定义,被传递给decorator函数,最终在wrapper函数中使用。被装饰的函数的参数,需要在wrapper函数中定义为*args
和**kwargs
,以支持任意数量和类型的参数,并在调用被装饰的函数时传递给它。
6.执行结果:
五、装饰器执行过程
像上面的函数,在代码执行时,会首先将**@cache**
加载(函数定义时),执行cache中的内容,不执行wrapper的内容(函数调用时)。后续fibonacci函数执行时,会先执行对应的wrapper的内容,再执行函数本身,即cache中的cached_results类似于wrapper的全局变量
后续的多次调用中,cache中仅wrapper的内容在执行
六、装饰器的执行顺序
说法一:装饰顺序按由下到上,调用时由上到下,执行顺序和装饰顺序相反。(简单记成正常的代码顺序即可)
说法二:装饰器由下到上依次立马执行,之后我们调用的f已经是被装饰器执行了之后的f了,此时是由上到下返回去依次调用。整个过程有点像先上楼梯(装饰过程),再下楼梯(调用函数)
def decorator_a(func):
print('Get in decorator_a')
def inner_a(*args, **kwargs):
print('Get in inner_a')
return func(*args, **kwargs)
return inner_a
def decorator_b(func):
print('Get in decorator_b')
def inner_b(*args, **kwargs):
print('Get in inner_b')
return func(*args, **kwargs)
return inner_b
@decorator_b
@decorator_a
def f(x):
print('Get in f')
return x * 2
f(1)
执行结果:
关于执行顺序可参考:https://segmentfault.com/a/1190000007837364