楼主: CDA网校
110 0

[每天一个数据分析师] 如何编写高效的 Python 数据类 [推广有奖]

管理员

已卖:189份资源

泰斗

2%

还不是VIP/贵宾

-

威望
3
论坛币
116127 个
通用积分
9985.9629
学术水平
268 点
热心指数
276 点
信用等级
243 点
经验
227847 点
帖子
6876
精华
19
在线时间
4367 小时
注册时间
2019-9-13
最后登录
2025-12-24

初级热心勋章

楼主
CDA网校 学生认证  发表于 昨天 14:19 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

编写高效的Python数据类既能简化模板,又能保持代码的简洁。而本文将教你如何做到这一点。

介绍

标准的 Python 对象将属性存储在实例字典中。除非你手动实现哈希,否则它们是不可哈希的,而且默认会比较所有属性。这种默认行为合理,但对于创建多个实例或需要对象作为缓存键的应用程序来说,并不优化。

数据类通过配置而非自定义代码来解决这些限制。你可以用参数来改变实例的行为和内存使用量。字段级设置还允许你排除属性进行比较,定义可变值的安全默认值,或控制初始化的工作方式。

本文重点介绍了那些在不增加复杂性的情况下提升效率和可维护性的关键数据类能力。

你可以在GitHub上找到代码。

1. 用于哈希性和安全性的冻结数据类

将数据类设置为不可变,便可实现哈希性。这允许你将实例用作字典键或将其存储成集合,如下所示:

from dataclasses import dataclass

@dataclass(frozen=True)
class CacheKey:
    user_id: int
    resource_type: str
    timestamp: int
    
cache = {}
key = CacheKey(user_id=42, resource_type="profile", timestamp=1698345600)
cache[key] = {"data""expensive_computation_result"}

该参数使所有字段在初始化后变得不可变,并自动实现 。没有它,你在尝试使用实例作为字典键时会遇到 a。frozen=True__hash__()TypeError

这种模式对于构建缓存层、去重逻辑或任何需要哈希类型的数据结构至关重要。不可变性还防止了状态意外被修改的整类错误。

2. 内存效率插槽

当你实例化成千上万的对象时,内存开销会迅速增加。这里有一个例子:

from dataclasses import dataclass

@dataclass(slots=True)
class Measurement:
    sensor_id: int
    temperature: float
    humidity: float

该参数消除了 Python 通常创建的每个实例。槽不将属性存储在字典中,而是使用更紧凑的固定大小数组。slots=True__dict__

对于这样简单的数据类,你每个实例节省了几个字节,并且能更快地访问属性。权衡是你不能动态添加新属性。

3. 带有场参数的自定义等式

通常不需要所有字段都能参与等式检查。这在处理元数据或时间戳时尤其如此,如下示例:

from dataclasses import dataclass, field
from datetime import datetime

@dataclass
class User:
    user_id: int
    email: str
    last_login: datetime = field(compare=False)
    login_count: int = field(compare=False, default=0)

user1 = User(1, "alice@example.com", datetime.now(), 5)
user2 = User(1, "alice@example.com", datetime.now(), 10)
print(user1 == user2) 

输出:

True

字段上的参数将其排除在自动生成方法之外。compare=False__eq__()

这里,如果两个用户共享相同的ID和邮箱,无论他们何时登录或登录次数,都被视为平等。这防止了在比较代表同一逻辑实体但追踪元数据不同的对象时出现的虚假不等式。

4. 工厂功能与默认工厂

在函数签名中使用可变默认是 Python 的陷阱。数据类提供了一个简洁的解决方案:

from dataclasses import dataclass, field

@dataclass
class ShoppingCart:
    user_id: int
    items: list[str] = field(default_factory=list)
    me tadata: dict = field(default_factory=dict)

cart1 = ShoppingCart(user_id=1)
cart2 = ShoppingCart(user_id=2)
cart1.items.append("laptop")
print(cart2.items)

该参数会调用一个可调用的变量,为每个实例生成新的默认值。没有它,使用时会创建一个跨所有实例的共享列表——经典的可变默认陷阱!default_factoryitems: list = []

该模式适用于列表、字典、集合或任何可变类型。你也可以通过自定义工厂函数来实现更复杂的初始化逻辑。

5. 初始化后处理

有时你需要在自动生成的运行后推导字段或验证数据。以下是你可以通过钩子实现这一点的方法:__init__post_init

from dataclasses import dataclass, field

@dataclass
class Rectangle:
    width: float
    height: float
    area: float = field(init=False)
    
    def __post_init__(self):
        self.area = self.width * self.height
        if self.width <= 0 or self.height <= 0:
            raise ValueError("Dimensions must be positive")

rect = Rectangle(5.0, 3.0)
print(rect.area)

生成后,方法立即运行。面积上的参数阻止它成为一个参数。post_init____init__init=False__init

这种模式非常适合计算字段、验证逻辑或输入数据归一化。你也可以用它来变换场,或者建立依赖于多个场的不变量。

6. 带序参数的排序

有时候,你需要数据类实例是可排序的。这里有一个例子:

from dataclasses import dataclass

@dataclass(order=True)
class Task:
    priority: int
    name: str
    
tasks = [
    Task(priority=3, name="Low priority task"),
    Task(priority=1, name="Critical bug fix"),
    Task(priority=2, name="Feature request")
]

sorted_tasks = sorted(tasks)
for task in sorted_tasks:
    print(f"{task.priority}: {task.name}")

输出:

1: Critical bug fix
2: Feature request
3: Low priority task

该参数基于场序生成比较方法(, , , )。字段是从左到右比较的,因此在此例中优先于名称。order=True__lt____le____gt____ge__

该功能允许你自然排序集合,无需编写自定义比较逻辑或关键函数。

7. 场排序与初始化变异

当初始化逻辑需要不应成为实例属性的值时,你可以使用,如下所示:InitVar

from dataclasses import dataclass, field, InitVar

@dataclass
class DatabaseConnection:
    host: str
    port: int
    ssl: InitVar[bool] = True
    connection_string: str = field(init=False)
    
    def __post_init__(self, ssl: bool):
        protocol = "https" if ssl else "http"
        self.connection_string = f"{protocol}://{self.host}:{self.port}"

conn = DatabaseConnection("localhost", 5432, ssl=True)
print(conn.connection_string)  
print(hasattr(conn, 'ssl'))

输出:

https://localhost:5432
False

类型提示标记了一个参数,该参数传递给 和,但不成为字段。这样既能保持实例的干净,又允许复杂的初始化逻辑。该标志影响我们如何构建连接串,但此后无需持续存在。InitVar__init____post_init__ssl

何时不使用数据类

数据类并不总是合适的工具。以下情况下不要使用数据类:

你需要复杂的继承层级结构,并有跨多层的自定义逻辑__init__ 你是在构建具有显著行为和方法的类(域对象用普通类)。 你需要像Pydantic或attrs这样的库提供的验证、序列化或解析功能 你正在处理涉及复杂状态管理或生命周期要求的课程 数据类作为轻量级数据容器而非功能齐全的域对象,效果最佳。

结论

写高效的数据类是理解它们的选项如何相互作用,而不是全部死记硬背。知道何时以及为何使用每个功能,比记住每一个参数更重要。

正如文章中讨论的,利用不可变性、槽位、字段自定义和初始化后钩子等特性,可以写出精简、可预测且安全的Python对象。这些模式有助于防止错误并减少内存开销,同时不增加复杂性。

通过这些方法,数据类可以让你编写干净、高效且易于维护的代码。祝你编程愉快!

推荐学习书籍 《CDA一级教材》适合CDA一级考生备考,也适合业务及数据分析岗位的从业者提升自我。完整电子版已上线CDA网校,累计已有10万+在读~ !

免费加入阅读:https://edu.cda.cn/goods/show/3151?targetId=5147&preview=0

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝


您需要登录后才可以回帖 登录 | 我要注册

本版微信群
加好友,备注cda
拉您进交流群
GMT+8, 2025-12-25 07:05