Python 有 @dataclass 和 无装饰器 的对比,有 __post_init__ 和 无 __post_init__ 的对比
flyfish
有 @dataclass 和 无装饰器 的对比
@dataclass 是 Python 的语法糖,自动生成 init (初始化)、repr (打印)、eq (等值比较)等样板代码;
没有该装饰器,这些方法必须手动逐行编写,太麻烦了
cpp
# ====================== 带 @dataclass======================
from dataclasses import dataclass
@dataclass
class Student:
# 仅声明属性+类型,自动生成重要的方法
name: str
age: int
score: float
# ====================== 不带 @dataclass======================
class StudentManual:
# 手动写初始化方法
def __init__(self, name: str, age: int, score: float):
self.name = name
self.age = age
self.score = score
# 手动写打印方法(不写则打印内存地址)
def __repr__(self):
return f"StudentManual(name='{self.name}', age={self.age}, score={self.score})"
# 手动写等值比较(不写则==比较内存地址,而非属性值)
def __eq__(self, other):
if not isinstance(other, StudentManual):
return False
return (self.name == other.name
and self.age == other.age
and self.score == other.score)
# ====================== 测试效果======================
if __name__ == '__main__':
# 测试dataclass
s1 = Student("张三", 18, 95.5)
s2 = Student("张三", 18, 95.5)
print("dataclass打印:", s1) # 友好打印
print("dataclass等值比较:", s1 == s2) # True(按属性值比较)
# 测试手动类
m1 = StudentManual("张三", 18, 95.5)
m2 = StudentManual("张三", 18, 95.5)
print("\n手动类打印:", m1)
print("手动类等值比较:", m1 == m2)
输出
cpp
dataclass打印: Student(name='张三', age=18, score=95.5)
dataclass等值比较: True
手动类打印: StudentManual(name='张三', age=18, score=95.5)
手动类等值比较: True
有 __post_init__ 和 无 __post_init__ 的对比
__post_init__ 是 @dataclass 专属的初始化后钩子方法:
dataclass 自动生成的 __init__ 执行完毕后,自动调用 __post_init__,用于:数据格式化、属性验证、计算衍生属性等。
cpp
from dataclasses import dataclass
# ====================== 没有 __post_init__ ======================
@dataclass
class User:
username: str
age: int
# 测试:需要手动处理额外逻辑
u = User("tom", 17)
# 手动格式化用户名(大写)
u.username = u.username.upper()
# 手动验证年龄
if u.age < 18:
print("无__post_init__:手动验证失败 → 年龄必须大于等于18")
# ====================== 有 __post_init__ (自动处理)======================
@dataclass
class UserAuto:
username: str
age: int
# 初始化完成后自动执行!无需手动调用
def __post_init__(self):
# 1. 自动格式化:用户名转大写
self.username = self.username.upper()
# 2. 自动数据验证:年龄必须≥18
if self.age < 18:
raise ValueError(f"有__post_init__:自动验证失败 → 年龄{self.age}无效")
# ====================== 测试效果 ======================
if __name__ == '__main__':
print("\n========= 有__post_init__ 测试 =========")
try:
# 创建对象时,自动执行__post_init__
user = UserAuto("jerry", 17)
print(user)
except ValueError as e:
print(e)
# 合法数据
user2 = UserAuto("lily", 20)
print("自动格式化后的用户:", user2) # username自动变成LILY
输出
cpp
无__post_init__:手动验证失败 → 年龄必须大于等于18
========= 有__post_init__ 测试 =========
有__post_init__:自动验证失败 → 年龄17无效
自动格式化后的用户: UserAuto(username='LILY', age=20)
对象初始化完成后自动执行计算
cpp
from dataclasses import dataclass
# ====================== 场景1:无 @dataclass(纯手写,最繁琐)======================
class ProductManual:
# 手动写初始化方法
def __init__(self, name: str, unit_price: float, quantity: int):
self.name = name # 基础属性:商品名
self.unit_price = unit_price # 基础属性:单价
self.quantity = quantity # 基础属性:数量
# 手动计算 衍生属性:总价
self.total_price = self.unit_price * self.quantity
# 手动写打印格式
def __repr__(self):
return f"ProductManual(name='{self.name}', unit_price={self.unit_price}, quantity={self.quantity}, total_price={self.total_price})"
# ====================== 场景2:有 @dataclass,无 __post_init__(需手动算衍生属性)======================
@dataclass
class ProductNoPost:
# 仅定义基础属性,自动生成__init__/__repr__
name: str
unit_price: float
quantity: int
# 【没有】__post_init__,无法自动计算总价
# ====================== 场景3:有 @dataclass + 有 __post_init__(自动算衍生属性,最优)======================
@dataclass
class ProductWithPost:
# 仅定义基础属性
name: str
unit_price: float
quantity: int
# 衍生属性(无需在上方声明,初始化后自动生成)
total_price: float = None # 可选:提前声明,代码更清晰
# 自动执行,计算衍生属性
def __post_init__(self):
# 自动计算总价(单价×数量)
self.total_price = self.unit_price * self.quantity
# ====================== 测试三种场景的效果 ======================
if __name__ == '__main__':
print("===== 场景1:无@dataclass(手动计算总价)=====")
p1 = ProductManual("笔记本", 5000, 2)
print(p1) # 直接有总价:自动算好
print("\n===== 场景2:有@dataclass,无__post_init__(无总价,需手动算)=====")
p2 = ProductNoPost("手机", 3000, 3)
print(p2) # 打印结果:没有total_price属性
#缺点:必须手动计算
p2.total_price = p2.unit_price * p2.quantity
print("手动计算后:", p2)
print("\n===== 场景3:有@dataclass+__post_init__(自动计算总价,一步到位)=====")
p3 = ProductWithPost("耳机", 200, 5)
print(p3) # 直接生成总价,无需任何手动操作
输出
cpp
===== 场景1:无@dataclass(手动计算总价)=====
ProductManual(name='笔记本', unit_price=5000, quantity=2, total_price=10000)
===== 场景2:有@dataclass,无__post_init__(无总价,需手动算)=====
ProductNoPost(name='手机', unit_price=3000, quantity=3)
手动计算后: ProductNoPost(name='手机', unit_price=3000, quantity=3)
===== 场景3:有@dataclass+__post_init__(自动计算总价,一步到位)=====
ProductWithPost(name='耳机', unit_price=200, quantity=5, total_price=1000)
多参数组合
init=True(默认):属性会加入自动生成的 __init__,创建对象时必须传参/设置默认值;
init=False:不加入构造函数 ,创建对象时不能传参,必须在 __post_init__ 中赋值。
repr=True(默认):打印对象时显示该属性;
repr=False:打印时隐藏该属性。
cpp
from dataclasses import dataclass, field
import uuid
@dataclass
class Order:
# 基础属性
user_name: str
goods_name: str
price: float
# 1. 衍生属性:订单总价
total_price: float = field(init=False)
# 2. 敏感属性:唯一订单ID,隐藏+禁止外部传参
order_id: str = field(init=False, repr=False)
# 3. 可变属性:订单标签
tags: list = field(default_factory=list)
def __post_init__(self):
# 自动赋值
self.total_price = self.price
self.order_id = str(uuid.uuid4())
# 测试
if __name__ == '__main__':
order = Order("张三", "蓝牙耳机", 199)
order.tags.append("热销")
print(order)
print("订单唯一ID:", order.order_id)
输出
cpp
Order(user_name='张三', goods_name='蓝牙耳机', price=199, total_price=199, tags=['热销'])
订单唯一ID: 0aa782c8-069a-488e-85d1-106f05fa1be2