Skip to content

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.age

dataclass 帮你自动生成这些

python
from 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 语句的魔法

你一定见过这种代码:

python
with 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:
    pass

LangChain 应用

  • API 调用计时
  • 数据库连接管理
  • 临时文件处理

本节小结

概念一句话解释记忆口诀
dataclass自动生成 init 等方法dataclass = 省代码
field()控制字段的行为field = 字段设置
frozen=True创建不可变对象frozen = 冻结
slots=True省内存slots = 瘦身
post_init初始化后自动执行post = 后置
@property方法伪装成属性property = 属性
enter/exitwith 语句的魔法with = 自动清理
描述符属性的守门员descriptor = 验证
元类类的类metaclass = 造类机器

新手学习建议

必学:dataclass、@property、with 语句 进阶:field()、frozen、slots 高级:描述符、元类(用到时再学)


下一节:3.5 实战:自定义 Tool

基于 MIT 许可证发布。内容版权归作者所有。