Administrator
Administrator
发布于 2025-11-06 / 4 阅读

python 定义数据模型@dataclass

dataclass装饰器可以自动生成特殊方法,让类定义非常简洁,同时保持极强的可读性和可扩展性。这是现代 Python 中表示复杂数据的首选方式

from dataclasses import dataclass
from typing import List

@dataclass
class OrderItem:
    product_id: int
    name: str
    quantity: int
    price: float

@dataclass
class Order:
    order_id: str
    customer: str
    items: List[OrderItem]  # 嵌套数据类,结构更清晰
    total: float
    status: str  # 例如:Pending, Paid, Shipped

# 使用
orders = []
orders.append(Order("ORD001", "Alice", [OrderItem(1, "Laptop", 1, 5200.00)], 5200.00, "Pending"))

使用dataclass的持久化保存问题

方法1 手动

import json
from datetime import datetime, date
from dataclasses import dataclass
from typing import List

@dataclass
class OrderItem:
    product_id: int
    name: str
    quantity: int
    price: float

@dataclass
class Order:
    order_id: str
    customer_name: str
    items: List[OrderItem]  # 嵌套数据类
    total: float
    status: str = "pending"  # 带默认值的字段

保存

def dataclass_to_dict(obj):
    """将数据类实例(包括嵌套的)转换为字典。"""
    if hasattr(obj, '__dataclass_fields__'):  # 判断是否为数据类实例
        result = {}
        for field in obj.__dataclass_fields__.values():
            value = getattr(obj, field.name)
            # 递归调用自身处理字段值
            result[field.name] = dataclass_to_dict(value)
        return result
    elif isinstance(obj, list):  # 处理列表
        return [dataclass_to_dict(item) for item in obj]
    elif isinstance(obj, (datetime, date)):  # 处理日期时间对象
        return obj.isoformat()
    else:
        return obj  # 基础类型(str, int, float, bool)直接返回


def save_orders_to_json(orders, filename):
    """将订单对象列表保存到JSON文件。"""
    # 1. 先将数据类对象转换为字典列表
    orders_data = [dataclass_to_dict(order) for order in orders]
    # 2. 将字典列表写入JSON文件
    with open(filename, 'w', encoding='utf-8') as f:
        json.dump(orders_data, f, ensure_ascii=False, indent=4)

# 创建一些订单数据
order_list = [
    Order("ORD001", "Alice", 
          [OrderItem(101, "Python编程书", 1, 68.5),
           OrderItem(102, "无线鼠标", 2, 99.0)], 
          266.5, "paid"),
    Order("ORD002", "Bob",
          [OrderItem(103, "机械键盘", 1, 299.0)],
          299.0, "shipped")
]

# 保存到文件
save_orders_to_json(order_list, "orders.json")

恢复

def dict_to_dataclass(data, data_class):
    """将字典转换为指定的数据类实例。"""
    if not data:
        return None
    # 获取数据类的字段注解
    fields = data_class.__dataclass_fields__
    # 准备初始化参数:只包含数据类中定义的字段
    init_args = {}
    for field_name, field_type in fields.items():
        if field_name in data:
            value = data[field_name]
            # 如果字段本身是一个数据类,则递归转换
            if hasattr(field_type.type, '__dataclass_fields__'):
                init_args[field_name] = dict_to_dataclass(value, field_type.type)
            # 如果字段是列表,且列表元素是数据类
            elif (hasattr(field_type.type, '__origin__') and 
                  field_type.type.__origin__ is list and
                  hasattr(field_type.type.__args__[0], '__dataclass_fields__')):
                elem_class = field_type.type.__args__[0]
                init_args[field_name] = [dict_to_dataclass(item, elem_class) for item in value]
            else:
                init_args[field_name] = value
    return data_class(**init_args)

def load_orders_from_json(filename):
    """从JSON文件加载数据并恢复为订单对象列表。"""
    with open(filename, 'r', encoding='utf-8') as f:
        orders_data = json.load(f)  # 直接加载为Python列表和字典
    # 将字典列表转换回Order对象列表
    return [dict_to_dataclass(order_dict, Order) for order_dict in orders_data]

# 从文件加载订单
loaded_orders = load_orders_from_json("orders.json")
for order in loaded_orders:
    print(f"订单: {order.order_id}, 客户: {order.customer_name}, 商品数: {len(order.items)}")

方法二:使用 dataclasses-json库(推荐)

pip install dataclasses-json

在原有 @dataclass基础上,再添加一个 @dataclass_json装饰器。

from dataclasses import dataclass
from typing import List
from dataclasses_json import dataclass_json  # 引入关键装饰器

@dataclass_json
@dataclass
class OrderItem:
    product_id: int
    name: str
    quantity: int
    price: float

@dataclass_json
@dataclass
class Order:
    order_id: str
    customer_name: str
    items: List[OrderItem]
    total: float
    status: str = "pending"

保存

def save_orders_to_json_easy(orders, filename):
    """使用dataclasses-json库保存订单到JSON文件。"""
    # 利用装饰器注入的方法,直接序列化对象列表
    json_str = Order.schema().dumps(orders, many=True, indent=4)
    with open(filename, 'w', encoding='utf-8') as f:
        f.write(json_str)

# 使用相同的数据
save_orders_to_json_easy(order_list, "orders_easy.json")

恢复

def load_orders_from_json_easy(filename):
    """使用dataclasses-json库从JSON文件加载订单。"""
    with open(filename, 'r', encoding='utf-8') as f:
        json_str = f.read()
    # 一行代码即可将JSON字符串转换回Order对象列表
    return Order.schema().loads(json_str, many=True)

loaded_orders_easy = load_orders_from_json_easy("orders_easy.json")
for order in loaded_orders_easy:
    print(f"订单: {order.order_id}, 状态: {order.status}")

对于非标准数据(自定义类或者vtk.vtkActor等类)的持久化问题

方案:使用自定义编解码器(最常用)

这是最优雅和推荐的方法。核心思想是:为非标准数据类型编写专门的转换函数,告诉 dataclasses-json如何将它转换为基本的、可 JSON 序列化的数据类型(如字符串、字典),以及如何从这些数据中重新构建出该对象。

dataclasses-json库提供了 field_encoderfield_decoder钩子来实现这一点。

1. 基本步骤

  1. 编码器(Encoder):一个函数,接受你的非标准对象(如 vtkActor),返回一个可 JSON 序列化的表示(如一个包含关键参数的字典或一个唯一标识字符串)。

  2. 解码器(Decoder):一个函数,接受编码后的数据,将其还原为一个新的对象实例。

  3. 注册钩子:在 dataclass的字段中,使用 metadata来指定这个字段的编码器和解码器。

2. 具体代码示例

假设你有一个 vtk.vtkActor对象和一个自定义的 Vector3类。

from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config
from typing import Any
import vtk
import json

# 假设的自定义类
class Vector3:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    def __repr__(self):
        return f"Vector3({self.x}, {self.y}, {self.z})"

# 1. 为 vt.vtkActor 编写编解码器
def encode_vtk_actor(actor: vtk.vtkActor) -> dict:
    """将 vtkActor 编码为字典。这里只保存其关键属性,如位置和颜色。"""
    # 获取位置 (这是一个简化示例,实际属性更复杂)
    pos = actor.GetPosition()
    # 获取颜色 (可能需要通过property获取)
    prop = actor.GetProperty()
    color = prop.GetColor()
    return {
        'type': 'vtkActor',
        'position': {'x': pos[0], 'y': pos[1], 'z': pos[2]},
        'color': {'r': color[0], 'g': color[1], 'b': color[2]}
        # 可以根据需要保存更多属性,如朝向、缩放等
    }

def decode_vtk_actor(data: dict) -> vtk.vtkActor:
    """从字典数据解码,重建一个 vtkActor。"""
    actor = vtk.vtkActor()
    pos = data['position']
    actor.SetPosition(pos['x'], pos['y'], pos['z'])
    col = data['color']
    actor.GetProperty().SetColor(col['r'], col['g'], col['b'])
    return actor

# 2. 为自定义的 Vector3 类编写编解码器
def encode_vector3(vec: Vector3) -> list:
    """将 Vector3 对象编码为一个列表 [x, y, z]"""
    return [vec.x, vec.y, vec.z]

def decode_vector3(data: list) -> Vector3:
    """从列表 [x, y, z] 解码,重建 Vector3 对象"""
    return Vector3(data[0], data[1], data[2])

# 3. 定义数据类,并使用 metadata 注册编解码器
@dataclass_json
@dataclass
class SceneObject:
    name: str
    # 在字段的 metadata 中指定编解码器
    actor: vtk.vtkActor = field(metadata=config(
        encoder=encode_vtk_actor,
        decoder=decode_vtk_actor
    ))
    # 另一个例子:处理 Vector3
    offset: Vector3 = field(metadata=config(
        encoder=encode_vector3,
        decoder=decode_vector3
    ))

# 使用示例
if __name__ == "__main__":
    # 创建一些测试对象
    a_actor = vtk.vtkActor()
    a_actor.SetPosition(1, 2, 3)
    a_actor.GetProperty().SetColor(1.0, 0.0, 0.0) # 红色

    a_vector = Vector3(4, 5, 6)

    scene_obj = SceneObject(name="MyObject", actor=a_actor, offset=a_vector)

    # 序列化为 JSON 字符串 (此时会调用我们的编码器)
    json_str = scene_obj.to_json(indent=2)
    print("序列化后的JSON:")
    print(json_str)
    # 输出会类似于:
    # {
    #   "name": "MyObject",
    #   "actor": {
    #     "type": "vtkActor",
    #     "position": {"x": 1.0, "y": 2.0, "z": 3.0},
    #     "color": {"r": 1.0, "g": 0.0, "b": 0.0}
    #   },
    #   "offset": [4, 5, 6]
    # }

    # 从 JSON 字符串反序列化 (此时会调用我们的解码器)
    loaded_scene_obj = SceneObject.from_json(json_str)
    print("\n反序列化后的对象:")
    print(f"Name: {loaded_scene_obj.name}")
    print(f"Actor type: {type(loaded_scene_obj.actor)}") # 应该是 vtk.vtkActor
    print(f"Actor position: {loaded_scene_obj.actor.GetPosition()}")
    print(f"Offset: {loaded_scene_obj.offset}") # 应该是 Vector3(4, 5, 6)