文章目录
-
- [0. 前言](#0. 前言)
- [1. 为什么突然冒出"装饰器"?](#1. 为什么突然冒出“装饰器”?)
- [2. 装饰器的基础:嵌套函数(函数里还能再"生"函数)](#2. 装饰器的基础:嵌套函数(函数里还能再“生”函数))
- [3. 装饰器的常见用途](#3. 装饰器的常见用途)
- [4. 常用装饰器代码实例](#4. 常用装饰器代码实例)
-
- [4.1 最简骨架](#4.1 最简骨架)
- [4.2 缓存装饰器(官方现成的)](#4.2 缓存装饰器(官方现成的))
- [4.3 重试装饰器(带参数版)](#4.3 重试装饰器(带参数版))
- [5. 小结](#5. 小结)
0. 前言
📣按照国际惯例,首先声明:本文只是我自己学习的理解,虽然参考了他人的宝贵见解及成果,但是内容可能存在不准确的地方。如果发现文中错误,希望批评指正,共同进步。
本文通过实例介绍Python中的装饰器。
1. 为什么突然冒出"装饰器"?
举个真场景: 产品经理说:"把这个Python脚本中的每个函数的运行时间打印出来。"
你最直观的写法:
python
import time
def download():
t0 = time.time()
... # 真正的下载代码
print(f"download 花了 {time.time()-t0:.2f} 秒")
def upload():
t0 = time.time()
... # 真正的上传代码
print(f"upload 花了 {time.time()-t0:.2f} 秒")
问题立刻来了:
- 复制粘贴到吐,文件里全是
t0 = time.time()。 - 哪天老板要改成写日志,你得全部再改一遍。
- 真正的业务(下载/上传)被淹没在一堆"边角料"里。
- 最最关键的是coding大佬根本不允许你动他的代码,说你写的都是屎山!
装饰器就是来解决这种"想给函数加点通用小功能,又不想改它本身"的痛点。
2. 装饰器的基础:嵌套函数(函数里还能再"生"函数)
Python 里函数像字符串、数字一样,可以当变量传来传去 。
嵌套函数 = 在函数 内部 再 def 一个函数,通常用于:
- 隐藏实现细节
- 做闭包(记住外层变量的值)
- 给装饰器当"脚手架"
看个简单例子:
python
def make_adder(base): # 外层
def adder(x): # 内层
return base + x # 内层记住了 base
return adder # 把内层函数当"商品"返回
plus5 = make_adder(5) # 现在 plus5 是一个"加 5"机器
print(plus5(7)) # 12
plus5 手里一直攥着 base=5,这就是闭包 。
装饰器全靠这个本事:外层把原函数"记住",内层在前后加点料,再返回内层函数即可。
3. 装饰器的常见用途
装饰器是一种语法糖,他的作用就是:"不碰原函数一行代码,就能给它前后塞点额外操作。" 它的常见用途有以下几类:
- 打印日志 / 计时
- 缓存结果(斐波那契算过一次就记住)
- 重试网络请求
- 检查用户权限
- 路由注册(Flask、FastAPI)
语法糖的介绍请见:【单点知识】Python语法糖------让代码更甜的方法
4. 常用装饰器代码实例
4.1 最简骨架
python
import time, functools
def timer(func): # 外层:收到原函数
@functools.wraps(func) # 保留原函数名字&文档
def wrapper(*args, **kw): # 内层:真正会被调用的新函数
t0 = time.time()
result = func(*args, **kw) # 调原函数
print(f"{func.__name__} 花了 {time.time()-t0:.3f} 秒")
return result
return wrapper # 把新函数返还给外界
使用:贴贴纸即可,例如
python
import time, functools
import numpy as np
def timer(func): # 外层:收到原函数
@functools.wraps(func) # 保留原函数名字&文档
def wrapper(*args, **kw): # 内层:真正会被调用的新函数
t0 = time.time()
result = func(*args, **kw) # 调原函数
print(f"{func.__name__} 花了 {time.time()-t0:.3f} 秒")
return result
return wrapper # 把新函数返还给外界
@timer
def mm(x,y): return np.matmul(x,y)
if __name__ =='__main__':
x = np.random.rand(9999,9999)
y = np.random.rand(9999,9999)
mm(x, y)
输出为:
python
mm 花了 4.049 秒
4.2 缓存装饰器(官方现成的)
@lru_cache 把函数每次的计算结果自动存进一张"备忘录",下次撞见相同输入直接拿答案,绝不再算第二次。 加一行就能让递归、重复查询等代码从"龟速"变"秒回"。
python
from functools import lru_cache
import time
@lru_cache(maxsize=None)
def fib(n):
return n if n < 2 else fib(n-1) + fib(n-2)
t0 = time.time()
fib(999)
print(f"耗时:{time.time() - t0}:.10" )
t0 = time.time()
fib(1000)
print(f"耗时:{time.time() - t0}:.10")
输出为:
python
耗时:0.0010073184967041016:.10
耗时:0.0:.10
第一次fib(999):把 fib(0)...fib(999) 共 1000 个值全部算出并缓存。
第二次fib(1000) :只需算 1 个新值fib(1000) = fib(999) + fib(998),fib(999) 和fib(998)这两个直接从缓存拿。
4.3 重试装饰器(带参数版)
重装饰器就是先收参数再返回真正的装饰器,所以一眼看过去就是 @xxx(arg) 的双层结构。
python
def retry(times=3, wait=1):
def decorator(func):
@functools.wraps(func) #这不是重装饰器
def wrapper(*args, **kw):
for i in range(1, times+1):
try:
return func(*args, **kw)
except Exception as e:
print(f"第{i}次失败:{e}")
if i == times:
raise
time.sleep(wait)
return wrapper
return decorator
@retry(times=5)
def connect():
...
@functools.wraps(func)虽然"带括号",但它只是一次性返回真正装饰器的辅助工具,本身并不接受"用户自定义"参数,习惯上仍归为普通装饰器,而不是"带参装饰器(重装饰器)"。
5. 小结
- 嵌套函数 = 函数里
def函数 + 闭包记忆 - 装饰器 = 嵌套函数 + 返回新函数 +
@语法糖 - 口诀:"原函数不动,前后功能任意缝;撕掉装饰器,业务照样跑。"
记住:当你出现"又要给所有函数加同一件小事"的冲动时,先想装饰器------
写一次,贴万张,代码干净不慌张。