Python 装饰器

一、什么是装饰器

装饰器本质:接收函数作为参数,返回一个新函数,在不修改原函数代码、不改变调用方式的前提下,给原函数增加额外功能。
常见用途:日志记录、性能计时、权限校验、缓存、参数校验、异常捕获等。

核心特点

  1. 不修改原函数内部代码
  2. 不改变原函数调用方式
  3. 可以给函数动态增加功能

二、最简单的装饰器入门

1. 基础版:无参装饰器

python
 
运行
 
 
 
# 定义装饰器
def timer(func):
    # 内层包装函数
    def wrapper():
        import time
        start = time.time()
        func()  # 执行原函数
        end = time.time()
        print(f"函数执行耗时:{end - start:.4f}秒")
    return wrapper

# 使用装饰器语法糖 @
@timer
def work():
    for i in range(100000):
        pass

# 调用,和原来调用方式一模一样
work()
 
等价于:work = timer(work)

2. 原函数带参数怎么办?

*args, **kwargs 万能传参适配所有函数:
python
 
运行
 
 
 
def timer(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"耗时:{end - start:.4f}")
        return res  # 把原函数返回值返回出去
    return wrapper

@timer
def add(a, b):
    return a + b

print(add(1, 2))
 

三、保留原函数信息:functools.wraps

不加 wraps 时,add.__name__ 会变成 wrapper,调试会混乱。
python
 
运行
 
 
 
from functools import wraps

def timer(func):
    @wraps(func)  # 把原函数的属性复制给包装函数
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        res = func(*args, **kwargs)
        print(time.time() - start)
        return res
    return wrapper

@timer
def add(a, b):
    return a + b

print(add.__name__)  # 输出 add,而不是 wrapper
 

四、带参数的装饰器

需要给装饰器传配置参数时,多套一层函数:
python
 
运行
 
 
 
from functools import wraps

def logger(level):
    # 外层:接收装饰器参数
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{level}] 正在执行函数:{func.__name__}")
            return func(*args, **kwargs)
        return decorator
    return decorator

# 调用装饰器时传参
@logger(level="INFO")
def login():
    print("用户登录成功")

login()
 
执行流程:login = logger("INFO")(login)

五、多个装饰器叠加

python
 
运行
 
 
 
def decor1(func):
    def wrapper():
        print("装饰器1开始")
        func()
        print("装饰器1结束")
    return wrapper

def decor2(func):
    def wrapper():
        print("装饰器2开始")
        func()
        print("装饰器2结束")
    return wrapper

@decor1
@decor2
def test():
    print("原函数执行")

test()
 
执行顺序:
 
从上到下装饰,从下到上执行
plaintext
 
 
装饰器1开始
装饰器2开始
原函数执行
装饰器2结束
装饰器1结束
 

六、类装饰器(用类实现装饰)

依靠 __call__ 方法,实例可调用:
python
 
运行
 
 
 
class Timer:
    def __call__(self, func):
        from functools import wraps
        @wraps(func)
        def wrapper(*args, **kwargs):
            import time
            s = time.time()
            ret = func(*args, **kwargs)
            print(time.time() - s)
            return ret
        return wrapper

@Timer()
def demo():
    sum(range(100000))

demo()
 

七、常用实战场景示例

1. 权限校验装饰器

python
 
运行
 
 
 
from functools import wraps

def check_permission(role):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if user != role:
                raise PermissionError("权限不足")
            return func(user, *args, **kwargs)
        return decorator
    return decorator

@check_permission("admin")
def delete_data(user):
    print("删除数据成功")

# delete_data("guest")  # 抛异常
delete_data("admin")
 

2. 缓存装饰器(简易 lru 缓存)

python
 
运行
 
 
 
def cache(func):
    data = {}
    @wraps(func)
    def wrapper(*args):
        if args not in data:
            data[args] = func(*args)
        return data[args]
    return wrapper

@cache
def fib(n):
    if n <= 2:
        return 1
    return fib(n-1) + fib(n-2)

print(fib(50))
 

八、面试高频考点总结

  1. 装饰器本质:高阶函数 + 闭包
  2. 为什么用 functools.wraps:保留原函数名称、文档注释等元信息
  3. 带参装饰器需要三层函数,无参两层
  4. 多个装饰器:由下往上执行,由上往下装饰
  5. 类装饰器靠 __call__ 实现可调用
posted @ 2020-09-21 17:01  sjxm2017  阅读(61)  评论(0)    收藏  举报