🎀 20. 装饰器
本章概述
学习 Python 装饰器,在不修改原函数代码的情况下增强函数功能。 预计学习时间:60 分钟
20.1 什么是装饰器
装饰器是一种设计模式,用来在不修改原函数代码的情况下,给函数增加新功能。
装饰器的特点
- 不修改原函数的代码
- 不改变原函数的调用方式
- 可以给多个函数增加相同的功能
- 代码复用
20.2 函数是一等公民
在 Python 中,函数就是普通对象,可以:
- 赋值给变量
- 作为参数传递
- 作为返回值
- 定义在另一个函数里面
函数赋值给变量
def say_hello():
print("Hello")
f = say_hello # 函数赋值给变量
f() # 调用变量,相当于调用函数函数作为参数
def add(a, b):
return a + b
def calc(func, x, y):
return func(x, y)
result = calc(add, 3, 5) # 函数作为参数
print(result) # 8函数作为返回值
def outer():
def inner():
print("我是内部函数")
return inner # 返回函数
f = outer()
f() # 调用返回的函数闭包
内部函数引用外部函数的变量,外部函数返回内部函数:
def make_adder(n):
def adder(x):
return x + n # 引用了外部的n
return adder
add5 = make_adder(5)
print(add5(3)) # 8
print(add5(10)) # 15装饰器的基础
理解了「函数是一等公民」和「闭包」,装饰器就好理解了。
20.3 简单装饰器
手写装饰器
def my_decorator(func):
def wrapper():
print("函数执行前")
func() # 调用原函数
print("函数执行后")
return wrapper
def say_hello():
print("Hello!")
# 手动装饰
say_hello = my_decorator(say_hello)
say_hello()
# 输出:
# 函数执行前
# Hello!
# 函数执行后@ 语法糖
Python 提供了 @ 语法,更简洁:
def my_decorator(func):
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper
@my_decorator # 等价于 say_hello = my_decorator(say_hello)
def say_hello():
print("Hello!")
say_hello()20.4 装饰带参数的函数
固定参数
def my_decorator(func):
def wrapper(a, b): # 和原函数参数一样
print(f"参数:{a}, {b}")
result = func(a, b)
print(f"结果:{result}")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
print(add(3, 5))通用参数(推荐)
用 *args 和 **kwargs 接收任意参数,可以装饰任何函数:
def my_decorator(func):
def wrapper(*args, **kwargs): # 接收任意参数
print("函数执行前")
result = func(*args, **kwargs) # 传递参数
print("函数执行后")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
@my_decorator
def greet(name):
print(f"你好,{name}")
print(add(3, 5))
greet("Tom")推荐写法
装饰器的 wrapper 函数用
*args, **kwargs,可以装饰任何函数。
20.5 装饰器带参数
装饰器本身也可以接收参数。
示例
def repeat(times): # 装饰器的参数
def decorator(func): # 真正的装饰器
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3) # 装饰器带参数
def say_hello():
print("Hello!")
say_hello() # 会打印3次Hello!执行过程
# @repeat(3) 等价于:
# say_hello = repeat(3)(say_hello)
# 1. repeat(3) 返回 decorator 函数
# 2. decorator(say_hello) 返回 wrapper 函数20.6 多个装饰器
一个函数可以用多个装饰器,从下往上执行。
示例
def decorator1(func):
def wrapper(*args, **kwargs):
print("装饰器1:前")
result = func(*args, **kwargs)
print("装饰器1:后")
return result
return wrapper
def decorator2(func):
def wrapper(*args, **kwargs):
print("装饰器2:前")
result = func(*args, **kwargs)
print("装饰器2:后")
return result
return wrapper
@decorator1
@decorator2
def say_hello():
print("Hello!")
say_hello()输出:
装饰器1:前
装饰器2:前
Hello!
装饰器2:后
装饰器1:后
执行顺序
装饰器的执行顺序:离函数近的先执行 相当于
decorator1(decorator2(say_hello))
20.7 保留原函数信息
装饰器会把原函数替换成 wrapper 函数,原函数的名字、文档等信息会丢失。
问题
def my_decorator(func):
def wrapper(*args, **kwargs):
"""这是wrapper的文档"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def add(a, b):
"""这是add函数的文档"""
return a + b
print(add.__name__) # wrapper(不是add!)
print(add.__doc__) # 这是wrapper的文档(不是add的!)解决方案:functools.wraps
import functools
def my_decorator(func):
@functools.wraps(func) # 保留原函数信息
def wrapper(*args, **kwargs):
"""这是wrapper的文档"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def add(a, b):
"""这是add函数的文档"""
return a + b
print(add.__name__) # add(正确了!)
print(add.__doc__) # 这是add函数的文档(正确了!)好习惯
写装饰器时都加上
@functools.wraps(func),保留原函数信息。
20.8 常用装饰器场景
1. 计时装饰器
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 执行时间:{end - start:.4f}秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
slow_function()2. 日志装饰器
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"调用函数:{func.__name__}")
print(f"位置参数:{args}")
print(f"关键字参数:{kwargs}")
result = func(*args, **kwargs)
print(f"返回值:{result}")
return result
return wrapper
@log
def add(a, b):
return a + b
add(3, 5)3. 缓存装饰器
import functools
def cache(func):
cached = {}
@functools.wraps(func)
def wrapper(*args):
if args not in cached:
cached[args] = func(*args)
return cached[args]
return wrapper
@cache
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100)) # 很快,因为有缓存标准库也有
Python 3.2+ 有
functools.lru_cache,比自己写的更好用。from functools import lru_cache @lru_cache(maxsize=None) def fibonacci(n): ...
4. 权限验证装饰器
import functools
def require_login(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if not user.get("is_login"):
raise PermissionError("请先登录")
return func(user, *args, **kwargs)
return wrapper
@require_login
def view_profile(user):
print(f"查看{user['name']}的资料")20.9 类装饰器
用类当装饰器,需要实现 __call__ 方法。
示例
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"调用次数:{self.count}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello() # 调用次数:1
say_hello() # 调用次数:220.10 内置装饰器
@property
把方法变成属性,见 面向对象编程 - property
@staticmethod
静态方法,见 面向对象编程 - 静态方法
@classmethod
类方法,见 面向对象编程 - 类方法
@functools.lru_cache
缓存装饰器,见上文 20.8 节。
🔗 相关章节
- 上一章:迭代器与生成器
- 下一章:内置函数大全
- 闭包:函数详解 - 嵌套函数
- 设计模式:设计模式
📝 我的笔记
在这里记录你的理解、疑问和练习代码
# 你的练习代码
✅ 本章检查清单
- 理解函数是一等公民
- 理解闭包
- 掌握装饰器的基本用法
- 会写带参数的装饰器
- 会用多个装饰器
- 会用 functools.wraps 保留原函数信息
- 了解常见的装饰器应用场景
- 了解类装饰器
- 知道常用的内置装饰器