Python @dataclass 有 `__post_init__` 和 无 `__post_init__` 的对比

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
相关推荐
m0_747854521 天前
mysql如何设置数据库连接字符编码_修改default-character
jvm·数据库·python
Wyz201210241 天前
如何在 React 中正确将父组件函数传递给子组件并触发调用
jvm·数据库·python
2401_865439631 天前
Go语言如何用logrus_Go语言logrus日志框架教程【技巧】
jvm·数据库·python
西西弗Sisyphus1 天前
Python 在终端里彩色打印
开发语言·python·print·彩色打印
NotFound4861 天前
CSS如何利用Flex实现悬浮的侧边按钮组_利用fixed定位与flex布局组合
jvm·数据库·python
qq_189807031 天前
Golang怎么实现RBAC权限控制_Golang如何用casbin实现基于角色的访问控制系统【教程】
jvm·数据库·python
vegetablec1 天前
CSS如何处理相对定位留下的原本占位空白_认识到相对定位不会脱离文档流,需借助负margin消除视觉空隙
jvm·数据库·python
2401_832635581 天前
HTML怎么创建响应式图片备选方案_HTML srcset与sizes结构【详解】
jvm·数据库·python
2301_764150561 天前
Pandas GroupBy:将分组数据聚合为列表并赋值到新列
jvm·数据库·python
NotFound4861 天前
c++ 逆向工程ida pro c++如何使用ida pro插件和脚本
jvm·数据库·python