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_encoder和 field_decoder钩子来实现这一点。
1. 基本步骤
编码器(Encoder):一个函数,接受你的非标准对象(如
vtkActor),返回一个可 JSON 序列化的表示(如一个包含关键参数的字典或一个唯一标识字符串)。解码器(Decoder):一个函数,接受编码后的数据,将其还原为一个新的对象实例。
注册钩子:在
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)