3.4 dataclass 与高级特性
引言
小白理解 - dataclass 是什么?
普通类写起来很麻烦:
python# 传统写法:要写很多重复代码 class Person: def __init__(self, name, age): self.name = name self.age = age def __repr__(self): # 还得写打印格式 return f"Person(name={self.name}, age={self.age})" def __eq__(self, other): # 还得写比较逻辑 return self.name == other.name and self.age == other.agedataclass 帮你自动生成这些:
pythonfrom dataclasses import dataclass @dataclass class Person: name: str age: int # 就这么简单!__init__、__repr__、__eq__ 都自动有了一句话:dataclass = 省代码的数据容器
dataclass 基础
小白理解 - 看懂 dataclass 代码
python@dataclass # ← 加上这个装饰器 class AgentConfig: name: str # ← 必填字段(没有默认值) model: str # ← 必填字段 temperature: float = 0.7 # ← 选填字段(有默认值) tools: List[str] = field(default_factory=list) # ← 复杂默认值 # 创建对象 config = AgentConfig(name="Bot", model="gpt-4") print(config) # AgentConfig(name='Bot', model='gpt-4', temperature=0.7, ...)为什么
tools要用field(default_factory=list)?
- 错误:
tools: List[str] = []→ 所有对象共享同一个列表!- 正确:
field(default_factory=list)→ 每个对象创建新列表
python
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass
class AgentConfig:
"""Agent 配置(使用 dataclass)"""
name: str
model: str
temperature: float = 0.7
max_tokens: int = 2000
tools: List[str] = field(default_factory=list)
def __post_init__(self):
"""初始化后验证"""
if not 0 <= self.temperature <= 2:
raise ValueError("temperature 必须在 0-2 之间")
# 使用
config = AgentConfig(
name="ResearchBot",
model="gpt-4",
tools=["search", "calculator"]
)
print(config) # 自动生成 __repr__
print(config.temperature) # 访问属性
# 自动生成 __eq__
config2 = AgentConfig(name="ResearchBot", model="gpt-4")
print(config == config2) # True
__post_init__是什么?
- 在
__init__执行完毕后自动调用- 用于:数据验证、计算衍生属性
field() 高级用法
小白理解 - field() 的各种参数
参数 作用 例子 default_factory每次创建新对象 field(default_factory=list)compare=False比较时忽略此字段 id 字段不参与比较 repr=False打印时隐藏此字段 敏感数据不显示 init=False不通过 __init__传入自动计算的字段
python
from dataclasses import dataclass, field
from typing import List, Dict, Any
from datetime import datetime
@dataclass
class Message:
"""消息数据类"""
role: str
content: str
timestamp: datetime = field(default_factory=datetime.now)
metadata: Dict[str, Any] = field(default_factory=dict)
# 不包含在比较中
id: str = field(default="", compare=False)
# 不包含在 repr 中
raw_data: Dict = field(default_factory=dict, repr=False)
# 不初始化(由 __post_init__ 设置)
token_count: int = field(init=False)
def __post_init__(self):
"""计算 token 数量"""
self.token_count = len(self.content.split())
# 使用
msg = Message(role="user", content="Hello world")
print(msg) # 不显示 raw_data
print(f"Token 数量: {msg.token_count}")不可变 dataclass
小白理解 - frozen=True
frozen=True= 对象创建后不能修改类比:写好的合同,签完字就不能改了
为什么需要不可变?
- 防止意外修改敏感配置(如 API Key)
- 可以作为字典的 key(可变对象不能做 key)
- 多线程安全
python
from dataclasses import dataclass
@dataclass(frozen=True)
class ImmutableConfig:
"""不可变配置"""
api_key: str
model: str
temperature: float = 0.7
# 创建
config = ImmutableConfig(api_key="sk-xxx", model="gpt-4")
# 尝试修改会报错
try:
config.temperature = 0.5
except Exception as e:
print(f"错误: {e}") # FrozenInstanceError
# 可以作为字典的键
configs = {
config: "production"
}dataclass 与继承
python
from dataclasses import dataclass
from abc import ABC, abstractmethod
@dataclass
class BaseAgent(ABC):
"""Agent 基类"""
name: str
verbose: bool = False
@abstractmethod
def run(self, input: str) -> str:
"""运行 Agent"""
pass
@dataclass
class ChatAgent(BaseAgent):
"""聊天 Agent"""
model: str = "gpt-5"
temperature: float = 0.7
def run(self, input: str) -> str:
"""执行聊天"""
if self.verbose:
print(f"[{self.name}] 处理: {input}")
return f"响应: {input}"
# 使用
agent = ChatAgent(name="Assistant", model="gpt-3.5-turbo")
result = agent.run("你好")
print(result)asdict 和 astuple
python
from dataclasses import dataclass, asdict, astuple
import json
@dataclass
class Tool:
"""工具配置"""
name: str
enabled: bool
timeout: float = 30.0
@dataclass
class AgentSetup:
"""完整 Agent 设置"""
name: str
tools: List[Tool]
max_iterations: int = 10
# 创建对象
setup = AgentSetup(
name="ResearchBot",
tools=[
Tool(name="search", enabled=True),
Tool(name="calculator", enabled=False, timeout=10.0)
]
)
# 转换为字典
setup_dict = asdict(setup)
print(json.dumps(setup_dict, indent=2))
# 转换为元组
setup_tuple = astuple(setup)
print(setup_tuple)slots 优化内存
python
from dataclasses import dataclass
@dataclass(slots=True)
class OptimizedMessage:
"""内存优化的消息类"""
role: str
content: str
timestamp: float
# 使用 slots 可以减少内存占用
# 对比内存使用
import sys
# 不使用 slots
@dataclass
class RegularMessage:
role: str
content: str
timestamp: float
regular = RegularMessage("user", "hello", 1234567890.0)
optimized = OptimizedMessage("user", "hello", 1234567890.0)
print(f"Regular: {sys.getsizeof(regular)} bytes")
print(f"Optimized: {sys.getsizeof(optimized)} bytes")高级类特性
小白提示:以下内容是进阶知识,初学可以跳过,等有需要时再回来看。
1. init_subclass
小白理解 -
__init_subclass__当有人继承你的类时,自动执行某些操作
类比:加盟店注册系统
- 总部有一个登记本
- 每开一家新店(子类),自动记录到登记本
- 之后可以根据名字找到任何一家店
python
from typing import Dict, Type
class RegistryMixin:
"""自动注册子类"""
_registry: Dict[str, Type] = {}
def __init_subclass__(cls, **kwargs):
"""子类创建时自动注册"""
super().__init_subclass__(**kwargs)
cls._registry[cls.__name__] = cls
@classmethod
def get_class(cls, name: str) -> Type:
"""根据名称获取类"""
return cls._registry.get(name)
@dataclass
class BaseTool(RegistryMixin):
"""工具基类"""
name: str
@dataclass
class SearchTool(BaseTool):
"""搜索工具"""
max_results: int = 5
@dataclass
class CalculatorTool(BaseTool):
"""计算工具"""
precision: int = 2
# 自动注册
print(BaseTool._registry.keys()) # dict_keys(['BaseTool', 'SearchTool', 'CalculatorTool'])
# 根据名称创建实例
ToolClass = BaseTool.get_class("SearchTool")
tool = ToolClass(name="google_search")
print(tool)LangChain 应用:Tool 的自动发现和注册机制
2. 描述符 (Descriptor)
小白理解 - 描述符
描述符 = 属性的"守门员",每次读写属性时自动触发
类比:银行账户的取款验证
- 你想取钱(设置属性)
- 守门员检查:余额够吗?(验证)
- 通过才放行
何时用? 需要在设置属性时自动验证
python
class Validated:
"""验证描述符"""
def __init__(self, validator):
self.validator = validator
self.name = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
if not self.validator(value):
raise ValueError(f"验证失败: {self.name} = {value}")
obj.__dict__[self.name] = value
class Temperature:
"""温度参数(带验证)"""
value = Validated(lambda x: 0 <= x <= 2)
def __init__(self, value: float):
self.value = value
# 使用
temp = Temperature(0.7)
print(temp.value) # 0.7
try:
temp.value = 3.0 # 会抛出 ValueError
except ValueError as e:
print(e)3. 元类 (Metaclass)
小白理解 - 元类
元类 = 类的类,控制类是如何被创建的
普通流程:
类 → 创建对象元类流程:元类 → 创建类 → 创建对象最常见用途:单例模式
- 无论
ConfigManager()调用多少次- 永远返回同一个对象
类比:全公司只有一个 HR 部门,不管谁申请都是同一个
python
class SingletonMeta(type):
"""单例元类"""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class ConfigManager(metaclass=SingletonMeta):
"""配置管理器(单例)"""
def __init__(self):
self.config = {}
def set(self, key: str, value: Any):
self.config[key] = value
def get(self, key: str) -> Any:
return self.config.get(key)
# 无论创建多少次,都是同一个实例
manager1 = ConfigManager()
manager2 = ConfigManager()
manager1.set("model", "gpt-4")
print(manager2.get("model")) # gpt-4
print(manager1 is manager2) # True属性装饰器
小白理解 - @property 完整版
python@property # 读取时触发 @xxx.setter # 赋值时触发 @xxx.deleter # 删除时触发类比:智能家居温控器
- 读温度(@property):显示当前温度
- 设温度(@setter):验证合理范围后设置
- 关温控(@deleter):重置为默认值
python
from typing import Optional
class Agent:
"""Agent 类(带属性)"""
def __init__(self, name: str):
self._name = name
self._model: Optional[str] = None
self._ready = False
@property
def name(self) -> str:
"""获取名称"""
return self._name
@property
def model(self) -> Optional[str]:
"""获取模型"""
return self._model
@model.setter
def model(self, value: str):
"""设置模型"""
if value not in ["gpt-4", "gpt-3.5-turbo", "claude-3"]:
raise ValueError(f"不支持的模型: {value}")
self._model = value
self._ready = True
@model.deleter
def model(self):
"""删除模型"""
self._model = None
self._ready = False
@property
def ready(self) -> bool:
"""是否就绪"""
return self._ready
# 使用
agent = Agent("Assistant")
print(agent.name) # Assistant
agent.model = "gpt-4"
print(agent.model) # gpt-4
print(agent.ready) # True
del agent.model
print(agent.ready) # False上下文管理器协议
小白理解 - with 语句的魔法
你一定见过这种代码:
pythonwith open("file.txt") as f: content = f.read() # 文件自动关闭,不需要 f.close()
with语句的秘密:
__enter__:进入 with 块时执行(如:打开文件)__exit__:离开 with 块时执行(如:关闭文件)类比:酒店入住
__enter__:办理入住,拿房卡__exit__:退房,自动结算(无论是正常退房还是发生火灾)
python
from typing import Optional
import time
class TimedOperation:
"""计时上下文管理器"""
def __init__(self, operation_name: str, verbose: bool = True):
self.operation_name = operation_name
self.verbose = verbose
self.start_time: Optional[float] = None
self.end_time: Optional[float] = None
def __enter__(self):
"""进入上下文"""
if self.verbose:
print(f"开始 {self.operation_name}...")
self.start_time = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""退出上下文"""
self.end_time = time.time()
elapsed = self.end_time - self.start_time
if exc_type is None:
if self.verbose:
print(f"{self.operation_name} 完成,耗时: {elapsed:.2f}秒")
else:
if self.verbose:
print(f"{self.operation_name} 失败: {exc_val}")
return False # 不抑制异常
@property
def elapsed(self) -> Optional[float]:
"""获取耗时"""
if self.start_time and self.end_time:
return self.end_time - self.start_time
return None
# 使用
with TimedOperation("API 调用"):
time.sleep(1)
# 执行操作...
print()
# 错误情况
try:
with TimedOperation("失败的操作") as timer:
time.sleep(0.5)
raise ValueError("模拟错误")
except ValueError:
passLangChain 应用:
- API 调用计时
- 数据库连接管理
- 临时文件处理
本节小结
| 概念 | 一句话解释 | 记忆口诀 |
|---|---|---|
| dataclass | 自动生成 init 等方法 | dataclass = 省代码 |
| field() | 控制字段的行为 | field = 字段设置 |
| frozen=True | 创建不可变对象 | frozen = 冻结 |
| slots=True | 省内存 | slots = 瘦身 |
| post_init | 初始化后自动执行 | post = 后置 |
| @property | 方法伪装成属性 | property = 属性 |
| enter/exit | with 语句的魔法 | with = 自动清理 |
| 描述符 | 属性的守门员 | descriptor = 验证 |
| 元类 | 类的类 | metaclass = 造类机器 |
新手学习建议
必学:dataclass、@property、with 语句 进阶:field()、frozen、slots 高级:描述符、元类(用到时再学)
下一节:3.5 实战:自定义 Tool