🐍 Tutorial 01 Decorators
📄 文件:
⬇ 下载 .py 文件tutorial_01_decorators.py"""
============================================================
Python 装饰器 零基础实战教程
============================================================
目标:从"完全不懂"到"能在 AI Agent 里用装饰器注册工具"
运行方式:python tutorial_01_decorators.py
学习方式:从上到下逐步阅读,每步都输出结果,理解后再看下一步
核心理解(一句话):装饰器 = 接受一个函数,返回一个新函数
类比:就像给手机套壳 —— 手机还是那个手机,但多了保护/外观功能
============================================================
"""
import functools
import time
from datetime import datetime
print("=" * 60)
print("第 1 步:理解「函数是一等公民」—— 函数可以当参数传递")
print("=" * 60)
def 打招呼(name):
return f"你好,{name}!"
def 加感叹(func, name):
"""接收一个函数作为参数,调用它并加感叹号"""
return func(name) + "!!!"
result = 加感叹(打招呼, "hangbo")
print(f"结果:{result}")
print("👉 看到了吗?「打招呼」这个函数被当作参数传给了「加感叹」")
print()
print("=" * 60)
print("第 2 步:函数内部可以定义函数,也可以返回函数")
print("=" * 60)
def 创建乘法器(n):
"""这个函数返回一个新函数!"""
def 乘n(x):
return x * n
return 乘n # 注意:返回的是函数本身,不是调用的结果
乘3 = 创建乘法器(3)
乘5 = 创建乘法器(5)
print(f"乘3(10) = {乘3(10)}")
print(f"乘5(10) = {乘5(10)}")
print("👉 创建乘法器(3) 返回了一个「把任何数乘以3」的函数")
print()
print("=" * 60)
print("第 3 步:最简单的装饰器 —— 组合上面两个概念")
print("=" * 60)
def 计时装饰器(原函数):
"""包装一个函数,让它执行时自动打印耗时"""
def 包装函数(*args, **kwargs):
开始 = time.time()
结果 = 原函数(*args, **kwargs)
耗时 = time.time() - 开始
print(f" ⏱ {原函数.__name__} 执行耗时:{耗时:.4f} 秒")
return 结果
return 包装函数
def 慢速加法(a, b):
time.sleep(1)
return a + b
# 方式一:手动包装(理解原理)
慢速加法_计时版 = 计时装饰器(慢速加法)
print("手动包装:")
print(f"结果:{慢速加法_计时版(3, 5)}")
print()
print("=" * 60)
print("第 4 步:@ 语法糖 —— 让代码更优雅")
print("=" * 60)
@计时装饰器 # 这一行等价于:慢速乘法 = 计时装饰器(慢速乘法)
def 慢速乘法(a, b):
time.sleep(0.5)
return a * b
print("@语法糖:")
print(f"结果:{慢速乘法(4, 7)}")
print("👉 @计时装饰器 放在 def 上面,Python 会自动完成包装")
print()
print("=" * 60)
print("第 5 步:装饰器的致命陷阱 —— functools.wraps")
print("=" * 60)
def 坏装饰器(原函数):
"""不带 wraps 的装饰器(有 bug)"""
def 包装(*args, **kwargs):
return 原函数(*args, **kwargs)
return 包装
def 好装饰器(原函数):
"""带 wraps 的装饰器(推荐)"""
@functools.wraps(原函数) # 保留原函数的元信息
def 包装(*args, **kwargs):
return 原函数(*args, **kwargs)
return 包装
@坏装饰器
def 函数A():
"""这是函数A的文档"""
pass
@好装饰器
def 函数B():
"""这是函数B的文档"""
pass
print(f"函数A.__name__ = {函数A.__name__}") # 输出"包装",丢失了原名!
print(f"函数A.__doc__ = {函数A.__doc__}") # None!
print(f"函数B.__name__ = {函数B.__name__}") # 正确:函数B
print(f"函数B.__doc__ = {函数B.__doc__}") # 正确:文档
print("👉 写装饰器永远加 @functools.wraps(原函数),否则丢失函数名和文档")
print()
print("=" * 60)
print("第 6 步:带参数的装饰器 —— 进阶必备")
print("=" * 60)
# 需求:有时候想重试 3 次,有时候想重试 5 次
# 需要「装饰器工厂」:一个返回装饰器的函数
def 重试(最大次数=3, 等待秒=1):
"""装饰器工厂:根据参数创建不同的重试装饰器"""
def 装饰器(原函数):
@functools.wraps(原函数)
def 包装(*args, **kwargs):
for i in range(最大次数):
try:
return 原函数(*args, **kwargs)
except Exception as e:
if i == 最大次数 - 1:
raise # 最后一次还失败就抛出
print(f" 重试 {i+1}/{最大次数}:{e}")
time.sleep(等待秒)
return 包装
return 装饰器
@重试(最大次数=3, 等待秒=0.5)
def 不稳定的网络请求():
global 调用次数
调用次数 += 1
if 调用次数 < 3:
raise ConnectionError("网络断开")
return "数据获取成功"
调用次数 = 0
print(f"结果:{不稳定的网络请求()}")
print()
print("=" * 60)
print("第 7 步:类装饰器 —— 另一种写法")
print("=" * 60)
class 记录调用次数:
"""用类做装饰器,可以保存状态(调用了多少次)"""
def __init__(self, 原函数):
self.原函数 = 原函数
self.次数 = 0
def __call__(self, *args, **kwargs):
self.次数 += 1
print(f" {self.原函数.__name__} 已被调用 {self.次数} 次")
return self.原函数(*args, **kwargs)
@记录调用次数
def 计算平方(x):
return x * x
print(f"计算平方(5) = {计算平方(5)}")
print(f"计算平方(6) = {计算平方(6)}")
print(f"计算平方(7) = {计算平方(7)}")
print()
print("=" * 60)
print("第 8 步:AI Agent 实战 —— 工具注册装饰器")
print("=" * 60)
print("这是你未来 AI Agent 的核心机制之一!")
print()
# 模拟 Agent 的工具注册表
工具注册表 = {}
def 工具(名称: str, 描述: str):
"""装饰器:把 Python 函数注册为 Agent 可用的工具"""
def 装饰器(func):
@functools.wraps(func)
def 包装(*args, **kwargs):
return func(*args, **kwargs)
工具注册表[名称] = {
"函数": 包装,
"描述": 描述,
"参数": func.__code__.co_varnames[:func.__code__.co_argcount]
}
return 包装
return 装饰器
# 注册工具 —— 看!和写普通函数一样简单
@工具("计算器", "执行四则运算,例如:计算 3 + 5 * 2")
def 计算(表达式: str):
return eval(表达式) # 生产环境用 ast.literal_eval 更安全
@工具("天气查询", "查询指定城市的天气")
def 查天气(城市: str):
# 模拟 API 调用
return {"城市": 城市, "温度": "25°C", "天气": "晴"}
@工具("文件搜索", "搜索包含关键词的文件")
def 搜文件(关键词: str):
return ["main.py", "config.py", "agent.py"]
# 打印注册表
print("已注册的工具:")
for 名称, 信息 in 工具注册表.items():
print(f" 🔧 {名称}:{信息['描述']}")
print(f" 参数:{信息['参数']}")
print()
# 调用工具
print(f"计算(3+5*2) = {计算('3+5*2')}")
print(f"查天气('北京') = {查天气('北京')}")
print()
print("=" * 60)
print("总结:装饰器核心要点")
print("=" * 60)
print("""
1. 装饰器本质:接受函数 → 返回新函数(第1-2步)
2. 基本写法:def 装饰器(func): ... return 包装函数(第3步)
3. @语法糖:@装饰器名 放在 def 上一行(第4步)
4. 必须加 @functools.wraps(原函数)(第5步)
5. 带参数:三层嵌套,最外层接收参数(第6步)
6. AI Agent 应用:用装饰器注册工具(第8步)
学完这个,去看 Day 2 的 Pydantic 和高级 typing,
你会发现自己能写出更规范的代码!
""")