一、函数装饰器入门
对应Cookbook章节 9.1、9.2、9.3 和9.8
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2023/8/4 18:44
# @Author : Maple
# @File : 01-函数装饰器入门.py
# Cookbook 9.1、9.2、9.3 和9.8
import time
from functools import wraps
def timethis(func):
'''
Decorator that reports the execution time.
'''
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end-start)
return result
return wrapper
@timethis
def countdown(n):
'''
Counts down
'''
print('hello')
while n > 0:
n -= 1
# 多层装饰器
def decorator1(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper
def decorator2(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper
@decorator1
@decorator2
def add(x, y):
return x + y
# @wraps装饰func的底层 到底发生了什么?
# Part1
"""
# 1.wrapped是被装饰的函数
# 2.WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')
# 3.WRAPPER_UPDATES = ('__dict__',)
# 4.返回一个 update_wrapper 的偏函数
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
Decorator factory to apply update_wrapper() to a wrapper function
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
"""
# Part2
"""
# 1. wrapper是装饰器被装饰后 返回的函数
# 2. updated = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
Update a wrapper function to look like the wrapped function
for attr in assigned:
try:
# 1. 取出`被装饰`函数的属性
value = getattr(wrapped, attr)
except AttributeError:
pass
else:
# 2.将第一步取出来的属性值 赋值给 wrapper,也就是说wrapper的元数据实际上替换成[被装饰的函数]了
# 以此实现了被装饰函数 元数据的保留
setattr(wrapper, attr, value)
for attr in updated:
# 1.update方法实现 字典值的更新,注意是 后面更新前面的(逻辑: 交叉的key,用后面的值更新前面的; 只存在于后面的key,添加到前面的字典 )
# 2.如果wrapped 有 ('__dict__',)属性[Tips个人理解:类才有dict属性,方法没有这个属性],那么就把wrapper的dict属性也替换成wrapped的
# 3.如果wrapped 没有 dict属性, getattr去默认值{},而空字典 对wrapper.__dict__'的值 不会有任何影响
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
return wrapper
"""
if __name__ == '__main__':
# 1. 简单装饰器测试
countdown(1000000)
print('===================')
# 2.解除装饰器
# 2-1 方法之一
"""
1.适用场景: 在函数已经有装饰器的情况下,但是又要单独测试函数本身的场景
2.必要条件1: 有@wraps修饰
2.必要条件2:只有一个装饰器,多个装饰器的情况解除之后的测试结果不可预测
"""
# 通过__wrapped__直接访问被包装的函数:即countdown自身(不会走装饰器里面的逻辑)
countdown.__wrapped__