python 之装饰器(decorator)及其分类
一、装饰器的实现过程
1、Decorator(装饰器)和 Wrapper(包装函数)
Decorator(装饰器)即 outer() 外部函数
装饰器(decorator)是一种设计模式,也是 Python 中的一个语言特性,用于在不修改原函数代码的前提下,增强或修改函数的行为。
装饰器本质上是一个可调用对象(通常是函数或类),它接收一个函数(或方法)作为参数,并返回一个新的函数或可调用对象。
Wrapper(包装函数)即 inner() 内部函数
wrapper 是装饰器内部定义的一个嵌套函数,它的作用是包装原函数的调用,在调用前后添加额外逻辑(如日志、计时、权限检查等)。
wrapper函数通常接受*args和**kwargs,以兼容任意参数的原函数。- 它在装饰器函数中被定义,并作为返回值返回。
- 它“包裹”了原函数的功能。
def timer_decorator(func):
def wrapper(*args, **kwargs): # ← 这就是 wrapper
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 执行耗时: {end - start:.4f} 秒")
return result
return wrapper
给index函数添加统计执行时间的功能
import time def index(): time.sleep(3) print('from index') def home(): time.sleep(2) print('from home') def func(): time.sleep(2) print('from home') def outer(func): # 外层函数 def get_time(): # 内层函数 # 1. 在函数执行之前打一个时间点 start_time = time.time() func() # index() # 2. 在函数执行完毕之后在打一个时间点 end_time = time.time() # 3. 两个时间的差值就是函数的实际执行时间 print("函数执行了:%s秒" % (end_time - start_time)) return get_time # 返回内层函数 # 利用闭包的形式来传参 # res = outer(index) # res:get_time的内存地址 # res() # get_time() home = outer(home) home()
2、
当执行不同的函数,计算不同函数的执行时间,有的函数有参数,有的函数没参数
def index(name, username): time.sleep(3) print('from index') def home(name): time.sleep(2) print('from home', name) def outer(func): def get_time(*args, **kwargs): # 1. 在函数执行之前打一个时间点 start_time = time.time() func(*args, **kwargs) # 原有函数 # 2. 在函数执行完毕之后在打一个时间点 end_time = time.time() # 3. 两个时间的差值就是函数的实际执行时间 print("函数执行了:%s秒" % (end_time - start_time)) return get_time index = outer(index) index('tony', username='tank')
3、
index = outer(index) 将index函数作为参数传到 outer 中
outer() 执行 ---> return get_time ---> res = func() ---> return res ---> res = index() 最后res就是原函数的返回值
def home(name):
time.sleep(2)
print('from home', name)
def index():
time.sleep(3)
print('from index')
return 'from index'
def outer(func):
def get_time(*args, **kwargs):
start_time = time.time()
res=func(*args, **kwargs) # index() func('tony')
end_time = time.time()
print("函数执行了:%s秒" % (end_time - start_time))
return res
return get_time
index = outer(index)
res=index()
print(res)
4、
def outer(func): def inner(*args, **kwargs): print('这个是函数执行之前可以添加的功能') res = func(*args, **kwargs) print('这个是函数执行之后可以添加的功能') return res return inner def index(): print('from index') index = outer(index) index()
5、
@decorator
def my_function():
print("Hello")
等价于:
def my_function():
print("Hello")
my_function = decorator(my_function)
简单的计时器装饰器
import time
def timer_decorator(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_decorator
def slow_function():
time.sleep(1)
print("函数执行完毕")
slow_function()
6、装饰器修复技术(@wrapper)
functools.wraps 函数可以拷贝原函数的属性信息。warps源码中通过使用partial函数复制了原函数的信息。
from functools import wraps
def my_decorator(func):
@wraps(func) # ← 保留原函数的元信息
def wrapper(*args, **kwargs):
print("调用前")
result = func(*args, **kwargs)
print("调用后")
return result
return wrapper
@my_decorator
def greet(name):
"""打招呼函数"""
print(f"Hello, {name}!")
print(greet.__name__) # 输出: greet(而不是 wrapper)
print(greet.__doc__) # 输出: 打招呼函数
7、双层语法糖
使用场景:将登录验证、计算函数执行时间两个功能附加给一个原函数
# 双层语法糖 import time # 登录验证功能 def login_auth(func): # func = index def auth(*args, **kwargs): # 写登录功能 # 1. username = input('username:').strip() password = input('password:').strip() # 2. 验证用户名和密码是否正确 if username == 'kevin' and password == '123': print('登录成功') res = func(*args, **kwargs) return res else: print('用户名或者密码错误') return auth # 计算函数执行时间 def get_time(func): # func = index def inner(*args, **kwargs): # func:index # 1. 在函数执行之前打一个时间点 start_time = time.time() res = func(*args, **kwargs) # index() func('tony') # 2. 在函数执行完毕之后在打一个时间点 end_time = time.time() # 3. 两个时间的差值就是函数的实际执行时间 print("函数执行了:%s秒" % (end_time - start_time)) return res return inner @login_auth # index=login_auth(get_time内部的inner函数名) @get_time # get_time内部的inner函数名,等价于get_time(index) def index(): time.sleep(2) print('from index') index() # auth()
从下往上执行,最外层语法糖使用下面的函数名,即index = login_auth(inner)

- 首先,装饰器
@get_time被执行,它将函数index作为参数传递给装饰器函数get_time。因为get_time是一个装饰器函数,它会返回一个新的函数inner来替换原来的函数index。 - 接着,装饰器
@login_auth被执行,它将函数inner作为参数传递给装饰器函数login_auth。因为login_auth也是一个装饰器函数,它会返回一个新的函数auth来替换原来的函数inner。 - 现在,函数
index已经被装饰成auth函数,即index = login_auth(get_time(index))。 - 当我们调用函数
index()时,实际上是调用被装饰后的auth函数。auth函数首先执行登录验证,验证通过后再执行原始函数inner。 - 函数
inner中,先记录下函数执行之前的时间,然后再执行原始函数index(即被装饰的函数)。在执行完原始函数后,再记录下函数执行之后的时间,并计算出函数实际执行的时间。 - 最后,函数
auth返回原始函数inner的返回值,即函数index的返回值。
index 实际上被双重装饰了,首先是被装饰成计算函数执行时间的函数 inner,然后再被装饰成登录验证的函数 auth。这种双重装饰的方式可以让多个装饰器按照特定顺序依次作用于同一个函数上,从而实现更加复杂的功能。
8、三层语法糖
def outter1(func1): print('加载了outter1') def wrapper1(*args, **kwargs): print('执行了wrapper1') res1 = func1(*args, **kwargs) return res1 return wrapper1 def outter2(func2): print('加载了outter2') def wrapper2(*args, **kwargs): print('执行了wrapper2') res2 = func2(*args, **kwargs) return res2 return wrapper2 def outter3(func3): print('加载了outter3') def wrapper3(*args, **kwargs): print('执行了wrapper3') res3 = func3(*args, **kwargs) return res3 return wrapper3 @outter1 # index = outter1(wrapper2) = wrapper1 @outter2 # wrapper2 = outter2(wrapper3) @outter3 # wrapper3 = outter3(index) def index(): print('from index') index() # wrapper1 被index 调用。index() 函数执行等于 wrapper1()函数执行
执行顺序的理解:

2、outter2 接着会被执行------> 加载了outter2 -------> 返回 wrapper2 给到 outter1 作为 func2 传参
3、outter1 接着会被执行------> 加载了outter1 -------> 返回 wrapper1 给到 index ,此时 index = wrapper1
4、index()调用相当于wrapper1(), 此时调用wrapper1()-------> 执行了wrapper1----->func1为wrapper2,
wrapper2()被调用 ---------------------------------->执行了wrapper2----->func2为wrapper3,
wrapper3()被调用---------------------------------->执行了wrapper3----->func3为index
index()被调用---------------------------------->‘from index’

二、有参装饰器
def decorator(func) 三层 wrapper(*args, **kwargs)
@outter就行。有参数@outter('实参')
# 有参装饰器就是带参数的装饰器 # 先前我们学习的装饰器就是无参装饰器:不到参数的装饰器 def outter(type): def login_auth(func): def auth(*args, **kwargs): # 写登录功能 # 1.接收用户输入账号密码 username = input('username:').strip() password = input('password:').strip() # 2. 验证用户名和密码是否正确 # with open('') """ 根据不同的参数用户名和密码来自于不同的位置 """ if type == 'file': print('用户名和密码来自于文件') elif type == 'mysql': print('用户名和密码来自于MySQL') elif type == 'oracle': print('用户名和密码来自于Oracle') # if username == 'kevin' and password == '123': # print('登录成功') # res = func(*args, **kwargs) # return res # else: # print('用户名或者密码错误') return auth return login_auth # 语法糖处添加带上参数,无参装饰器这里不用带() @outter('file') # @login_auth # 如果是index函数认证,用户名和密码来自于文件 login_auth(index, 1) def index(): print('from index') @outter('mysql') # 如果是home函数认证,用户名和密码来MySQL def home(): print('from home') @outter('oracle') # 如果是func函数认证,用户名和密码来oracle def func(): print('from func') # 调用函数,原有的调用方式不变 index() home() func()
常用于 缓存、计数、重试、单例
什么是类装饰器
类装饰器是指:一个类,实现了 __call__ 方法,使得该类的实例可以像函数一样被调用。当这个类被用作装饰器时,它接收被装饰的函数作为参数,并可以控制该函数的调用行为。
✅ 类装饰器的关键点:
- 类的
__init__(self, func)方法接收被装饰的函数。 - 类的
__call__(self, *args, **kwargs)方法实现函数调用时的包装逻辑。 - 实例必须是“可调用的”(即实现
__call__)。
类装饰器的基本结构
class MyClassDecorator:
def __init__(self, func):
self.func = func
# 可以在这里初始化一些状态变量
def __call__(self, *args, **kwargs):
# 调用前的逻辑
print("调用前处理...")
result = self.func(*args, **kwargs)
# 调用后的逻辑
print("调用后处理...")
return result
@MyClassDecorator
def my_function(x, y):
print(f"计算中: {x} + {y}")
return x + y
# 调用
my_function(3, 4)
3、统计函数调用次数
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"函数 '{self.func.__name__}' 被调用了 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello() # 调用第1次
say_hello() # 调用第2次
say_hello() # 调用第3次
4、缓存装饰器(Memoization)
class Cache:
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args, **kwargs):
# 仅支持不可变参数(如元组)作为 key
key = str(args) + str(sorted(kwargs.items()))
if key in self.cache:
print(f"缓存命中: {key}")
return self.cache[key]
result = self.func(*args, **kwargs)
self.cache[key] = result
print(f"缓存写入: {key}")
return result
@Cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(5)) # 第一次计算,会缓存中间结果
print(fibonacci(5)) # 第二次直接从缓存读取
5、带参数的类装饰器
有时候我们希望装饰器本身也能接收参数,比如 @retry(max_attempts=3)。这时可以用类作为外层容器。
import time
from functools import wraps
class Retry:
def __init__(self, max_attempts=3, delay=1):
self.max_attempts = max_attempts
self.delay = delay
def __call__(self, func):
# 这里返回一个可调用对象(可以是函数或另一个类)
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, self.max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"第 {attempt} 次尝试失败: {e}")
if attempt == self.max_attempts:
raise e
time.sleep(self.delay)
return wrapper
@Retry(max_attempts=3, delay=0.5)
def unstable_api():
import random
if random.random() < 0.7:
raise ConnectionError("网络不稳定")
print("请求成功!")
return "data"
# 测试
try:
unstable_api()
except:
print("最终失败")
注意:这个例子中,Retry 是一个类,但它本身不是直接装饰函数,而是先被调用(Retry(...))生成一个实例,该实例再作为函数装饰器使用。
6、类装饰器用于装饰类中的方法
class LogCalls:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
# 这个 __call__ 会被绑定后的方法调用
print(f"调用方法: {self.func.__name__}")
return self.func(*args, **kwargs)
def __get__(self, instance, owner):
# instance: 实例(如 c)
# owner: 类(如 Calculator)
if instance is None:
return self # 类直接访问时返回装饰器本身
# 返回一个绑定到 instance 的可调用对象(这里是偏函数)
from functools import partial
return partial(self, instance)
class Calculator:
@LogCalls
def add(self, a, b):
return a + b
c = Calculator()
print(c.add(2, 3)) # 输出:调用方法: add \n 5
7、用类装饰器实现单例模式(Singleton)
class Singleton:
def __init__(self, cls):
self.cls = cls
self.instance = None
def __call__(self, *args, **kwargs):
if self.instance is None:
self.instance = self.cls(*args, **kwargs)
# print(self.cls)
return self.instance
@Singleton
class Database:
def __init__(self):
print("创建数据库连接...")
self.db = None
db1 = Database()
db2 = Database()
print(db1 is db2) # True
补充 cls的理解
装饰器 @Singleton 会把 Database 这个类作为参数传递给 Singleton 装饰器的 __init__ 方法。
所以:
class Singleton:
def __init__(self, cls): # ← cls 就是被装饰的类(这里是 Database)
self.cls = cls # 把类保存下来
self.instance = None # 用于保存唯一的实例
def __call__(self, *args, **kwargs):
if self.instance is None:
self.instance = self.cls(*args, **kwargs) # 实例化这个类
return self.instance
四、一些实用装饰器
property 装饰器
作用:将类中的方法转换为“属性”来访问,实现对私有属性的安全访问(封装),同时保持调用的简洁性。
class Student:
def __init__(self, name, score):
self._name = name
self._score = score # 私有属性
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int) or value < 0 or value > 100:
raise ValueError("成绩必须是0-100之间的整数")
self._score = value
@property
def name(self):
return self._name.title() # 返回首字母大写
# 使用
s = Student("alice", 85)
print(s.score) # 85,像属性一样访问
s.score = 95 # 使用 setter 设置值
print(s.name) # Alice
补充:在类中传参的过程
class Student: 定义类后面的() 只能写继承的父类、而不是用来传传参,没有父类则不用写
s = Student("alice", 85)
其实 Python 做了两步:
-
调用
Student.__new__创建对象(一般不自己写,默认继承object的实现) -
调用
Student.__init__(self, name, score)初始化对象,这时候"alice"和85会传给name和score。
所以等价于:
obj = Student.__new__(Student) # 创建空对象 Student.__init__(obj, "alice", 85) # 初始化
2、
在 Web 开发中常用于检查用户是否已登录,未登录则跳转或拒绝访问。
from functools import wraps
# 模拟登录状态
current_user = None # 全局变量表示当前用户
def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
if current_user is None:
print("错误:请先登录!")
return None
return func(*args, **kwargs)
return wrapper
@login_required
def view_profile():
print(f"正在查看 {current_user} 的个人资料")
# 测试
view_profile() # 错误:请先登录!
current_user = "Alice"
view_profile() # 正在查看 Alice 的个人资料
3、lru_cache 缓存装饰器
作用:使用“最近最少使用”(LRU)算法缓存函数的返回结果,避免重复计算,显著提升性能,尤其适用于递归或耗时计算。
模块:functools.lru_cache
斐波那契数列优化
from functools import lru_cache
@lru_cache(maxsize=128) # 最多缓存 128 个不同参数的结果
def fib(n):
print(f"Calculating fib({n})")
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(10)) # 后续对 fib(10)、fib(9)... 会使用缓存
print(fib(10))
print(fib(20))
print(fib(30)) # 后续对 fib(20)、fib(19)... 会使用缓存
结果
from functools import lru_cache
@lru_cache(maxsize=128) # 最多缓存 128 个不同参数的结果
def fib(n):
print(f"Calculating fib({n})")
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(5)) # 后续对 fib(5)使用缓存
print(fib(5))
print(fib(10))
由结果可知:第二次调用fib(5)的时候已经有缓存,fib(10) 到fib(5) 处就使用缓存


浙公网安备 33010602011771号