13 dataclasses数据类
定义一个类来存储数据,你得写__init__、__repr__、__eq__……一堆重复代码。dataclasses模块就是来解决这个问题的——用装饰器自动生成这些方法,代码量直接砍掉一大半。
一、基本用法
1.1 @dataclass装饰器
python
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
# 自动生成了__init__
user = User("大志", 28, "test@example.com")
print(user) # User(name='大志', age=28, email='test@example.com')不用手写__init__,不用手写__repr__,装饰器全帮你搞定。
1.2 自动生成的方法
@dataclass默认生成以下方法:
| 方法 | 默认生成 | 说明 |
|---|---|---|
__init__ | 是 | 构造函数 |
__repr__ | 是 | 字符串表示 |
__eq__ | 是 | 相等比较 |
__lt__ 等 | 否 | 排序方法,需设置order=True |
__hash__ | 否 | 哈希值,需设置unsafe_hash=True |
二、字段定义
2.1 基本字段
python
from dataclasses import dataclass
@dataclass
class Config:
host: str = "localhost"
port: int = 8080
debug: bool = False
# 使用默认值
config1 = Config()
print(config1) # Config(host='localhost', port=8080, debug=False)
# 覆盖默认值
config2 = Config(host="0.0.0.0", port=9090, debug=True)2.2 field()函数
对于可变默认值(如list、dict),需要用field()的default_factory参数。
python
from dataclasses import dataclass, field
@dataclass
class Team:
name: str
members: list = field(default_factory=list)
tags: dict = field(default_factory=dict)
team = Team("AI团队")
team.members.append("大志")
print(team) # Team(name='AI团队', members=['大志'], tags={})2.3 field()的常用参数
python
from dataclasses import dataclass, field
@dataclass
class User:
name: str
age: int
password: str = field(repr=False) # 不在__repr__中显示
internal_id: int = field(init=False, default=0) # 不参与__init__
metadata: dict = field(default_factory=dict, repr=False)| 参数 | 说明 |
|---|---|
default | 默认值 |
default_factory | 可变默认值的工厂函数 |
init | 是否参与__init__(默认True) |
repr | 是否参与__repr__(默认True) |
compare | 是否参与比较(默认True) |
hash | 是否参与哈希(默认None) |
三、装饰器参数
3.1 常用参数
python
from dataclasses import dataclass
# 不可变数据类
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
p.x = 10 # AttributeError: cannot assign to field 'x'
# 生成排序方法
@dataclass(order=True)
class Student:
name: str
score: int
s1 = Student("大志", 85)
s2 = Student("小明", 92)
print(s1 < s2) # True(按score比较)
# 使用__slots__(3.10+)
@dataclass(slots=True)
class Optimized:
x: int
y: int3.2 装饰器参数一览
| 参数 | 默认值 | 说明 |
|---|---|---|
init | True | 生成__init__ |
repr | True | 生成__repr__ |
eq | True | 生成__eq__ |
order | False | 生成排序方法 |
unsafe_hash | False | 生成__hash__ |
frozen | False | 不可变(设置后不能修改字段) |
slots | False | 使用__slots__(3.10+) |
kw_only | False | 所有字段仅限关键字参数(3.10+) |
四、后处理
4.1 post_init
在__init__之后执行,适合做验证或计算派生字段。
python
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
rect = Rectangle(3, 4)
print(rect.area) # 12.04.2 InitVar
仅在初始化时使用的变量,不存储在实例中。
python
from dataclasses import dataclass, InitVar
@dataclass
class User:
name: str
raw_password: InitVar[str] # 仅初始化时使用
password_hash: str = field(init=False)
def __post_init__(self, raw_password):
self.password_hash = hash(raw_password)
user = User("大志", "123456")
print(user.password_hash) # 某个哈希值
# user.raw_password 不存在五、转换操作
5.1 asdict() / astuple()
python
from dataclasses import dataclass, asdict, astuple
@dataclass
class User:
name: str
age: int
user = User("大志", 28)
# 转为字典
print(asdict(user))
# {'name': '大志', 'age': 28}
# 转为元组
print(astuple(user))
# ('大志', 28)5.2 replace()
创建替换字段值的新实例。
python
from dataclasses import dataclass, replace
@dataclass
class Config:
host: str = "localhost"
port: int = 8080
debug: bool = False
config = Config()
new_config = replace(config, port=9090, debug=True)
print(new_config) # Config(host='localhost', port=9090, debug=True)5.3 fields()
获取所有字段信息。
python
from dataclasses import dataclass, fields
@dataclass
class User:
name: str
age: int
email: str
for f in fields(User):
print(f"{f.name}: {f.type}")
# name: <class 'str'>
# age: <class 'int'>
# email: <class 'str'>六、继承
python
from dataclasses import dataclass
@dataclass
class Animal:
name: str
age: int
@dataclass
class Dog(Animal):
breed: str
dog = Dog("旺财", 3, "柴犬")
print(dog) # Dog(name='旺财', age=3, breed='柴犬')有默认值的字段必须在没有默认值的字段之后:
python
from dataclasses import dataclass
@dataclass
class Base:
x: int
y: int = 0
@dataclass
class Derived(Base):
z: int = 0 # 正确:所有字段都有默认值
# w: int # 错误:无默认值的字段不能在有默认值的字段之后七、实用场景
7.1 配置管理
python
from dataclasses import dataclass, field
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
name: str = "mydb"
user: str = "admin"
password: str = ""
@dataclass
class AppConfig:
debug: bool = False
log_level: str = "INFO"
database: DatabaseConfig = field(default_factory=DatabaseConfig)
config = AppConfig(debug=True, database=DatabaseConfig(host="192.168.1.100"))7.2 API响应
python
from dataclasses import dataclass, field, asdict
@dataclass
class ApiResponse:
code: int = 200
message: str = "success"
data: dict = field(default_factory=dict)
def to_json(self):
return asdict(self)
response = ApiResponse(data={"name": "大志"})
print(response.to_json())
# {'code': 200, 'message': 'success', 'data': {'name': '大志'}}7.3 不可变数据
python
from dataclasses import dataclass
@dataclass(frozen=True)
class Vector:
x: float
y: float
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2
print(v3) # Vector(x=4, y=6)八、与普通类对比
同样的类,两种写法:
python
# 普通类
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def __repr__(self):
return f"User(name={self.name!r}, age={self.age!r})"
def __eq__(self, other):
if not isinstance(other, User):
return NotImplemented
return self.name == other.name and self.age == other.age
# dataclass
@dataclass
class User:
name: str
age: int代码量直接少了三分之二。
九、总结
dataclasses的核心:
| 功能 | 说明 |
|---|---|
@dataclass | 核心装饰器,自动生成方法 |
field() | 定义字段的详细属性 |
asdict() / astuple() | 转换为字典/元组 |
replace() | 创建替换字段值的新实例 |
fields() | 获取所有字段信息 |
__post_init__ | 初始化后处理 |
dataclasses是Python 3.7引入的,现在已经成为定义数据类的标准方式。如果你还在手写__init__,是时候换@dataclass了。