Python 在dataclasses 里,field() 能给可变、不可变数据分别设置安全的默认值
flyfish
Python 里有可变(Mutable) 和 不可变(Immutable)对象
Immutable(不可变对象)
一旦创建出来,里面的内容就不能改、不能动 ,硬改就直接报错。
Python 里自带的不可变类型:
整数int、浮点数float、布尔值bool、字符串str、元组tuple。
这类对象创建后,自身状态永远固定,想改只能新建一个。
例子:
字符串"Welcome",想直接改第1个字符,会报错;
元组(0,1,2,3),想把第1个值改成4,也会报错。
Mutable(可变对象)
创建之后,想改就改、想加就加、想删就删,完全灵活。
Python 里自带的可变类型:
列表list、字典dict、集合set,自己写的类一般也都是可变的。
对象本身可以被原地修改,不用新建副本。
例子:
列表[1,2,3],可以append加值、remove删值、直接改某一位;
字典{"name":"Ram"},可以直接修改age对应的值;
集合{1,2,3},可以add添加新元素。
区别
- 不可变对象:访问速度更快,但修改成本高(一改就必须造新的);
- 可变对象:修改极其方便,适合需要动态增删改的场景。
特殊例外
元组本身是不可变 的,但如果元组里装了列表 这种可变对象:
元组的元素不能替换、删除;
但元组里那个列表的内容,是可以修改的。
容器不可变,但容器里装的可变东西能改。
Python 所有数据都存在内存 里,id() 能查看内存地址:
- 不可变对象(Immutable)
创建后,内存里的原值不能改 !强行修改会创建一个新对象,内存地址变了。 - 可变对象(Mutable)
创建后,内存里的原值可以直接修改,对象还是原来的那个,内存地址不变。
用 id() 打印内存地址,验证:
1. 不可变对象(字符串 str)
python
# 不可变对象:字符串
a = "hello"
print("原始值:", a, " 内存地址:", id(a))
# 尝试修改 → 本质是创建新对象
a = a + " world"
print("修改后:", a, " 内存地址:", id(a))
结果
# 原始值: hello 内存地址: 1624662037328
# 修改后: hello world 内存地址: 1624662245040
地址变了 = 旧对象没改,新建了一个对象。
2. 可变对象(列表 list)
python
# 可变对象:列表
b = [1, 2, 3]
print("原始值:", b, " 内存地址:", id(b))
# 直接修改列表 → 原地修改
b.append(4)
print("修改后:", b, " 内存地址:", id(b))
结果
原始值: [1, 2, 3] 内存地址: 2302978101568
修改后: [1, 2, 3, 4] 内存地址: 2302978101568
地址没变 = 直接修改了原对象,没有新建!
| 类型 | 原理 | 内存地址 | 代表类型 |
|---|---|---|---|
| 不可变 | 不能改原值,修改=新建对象 | 会变化 | int/str/float/tuple |
| 可变 | 可以直接改原值,原地修改 | 不变 | list/dict/set |
dataclasses 使用 field() 操作这两类数据
在dataclasses 里,field() 能给**不可变(Immutable)与可变(Mutable)**两类对象分别设置安全的默认值
-
对不可变对象 (
int/str/float/bool/tuple):
field()通过default参数为其设置静态固定默认值,依托不可变对象无法被修改的特性,不存在数据共享污染风险,完全适配其底层特征。 -
对可变对象 (
list/dict/set):
field()通过default_factory参数动态生成独立实例作为默认值,规避了可变对象直接赋值默认值引发的跨实例数据共享问题,适配可变对象可原地修改的特性,保证数据隔离性。 -
通用行为控制:
field()的init/repr/compare等参数,可统一作用于可变与不可变两类对象,精细化控制其在数据类中的初始化、打印展示、等值比较等行为。
不可变类型(数字、字符串):直接用default=固定值;
可变类型(列表、字典、集合):不能直接写=[],必须用default_factory。
一、default:设置不可变类型默认值
为属性设置静态默认值 (仅支持 str/int/float/bool 等不可变类型)
属性有固定默认值,不需要动态生成,给 int/str/float/bool 这些不能修改的类型,设置一个固定的默认值,就叫不可变类型默认值。
不可变类型 = 一旦创建,里面的值就不能被修改的类型
不可变类型默认值 = 给这类属性设置固定的默认值,用 field(default=xx)
python
from dataclasses import dataclass,field
@dataclass
class Book:
name: str
# default:设置默认值(不可变类型专用)
price: float = field(default=29.9)
author: str = field(default="未知")
# 测试
if __name__ == '__main__':
book1 = Book("Python入门")
book2 = Book("Java教程", price=59.9)
print(book1)
print(book2)
运行结果
Book(name='Python入门', price=29.9, author='未知')
Book(name='Java教程', price=59.9, author='未知')
cpp
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
# 用 field(default=xx) 设置默认值
age: int = field(default=18)
gender: str = field(default="未知")
# 测试
if __name__ == '__main__':
s1 = Student("小明",gender="男")
s2 = Student("小红", gender="女")
print(s1)
print(s2)
运行结果
Student(name='小明', age=18, gender='男')
Student(name='小红', age=18, gender='女')
二、default_factory专门给 list / dict / set 这类可变类型设置默认值
default_factory 就是专门给列表、字典、集合生成独立默认值的工具,每次创建对象都会自动生成一个全新的空数据,让每个对象的默认数据互不干扰,不会出现数据错乱。
列表、字典、集合,想设默认值,只能用 default_factory
cpp
from dataclasses import dataclass, field
@dataclass
class StudentGood:
name: str
# 安全默认值:每次生成新列表
scores: list = field(default_factory=list)
# 扩展:字典、集合
info: dict = field(default_factory=dict)
tags: set = field(default_factory=set)
# 测试
if __name__ == '__main__':
s1 = StudentGood("小明")
s2 = StudentGood("小红")
s1.scores.append(90)
s1.info["school"] = "一中"
print("小明:", s1.scores, s1.info)
print("小红:", s2.scores, s2.info) # 数据隔离,无污染
运行结果
小明: [90] {'school': '一中'}
小红: [] {}
cpp
from dataclasses import dataclass, field
# 1. 列表默认值(最常用:分数、列表数据)
@dataclass
class Student:
name: str
# 空列表默认值
scores: list = field(default_factory=list)
# 2. 字典默认值(用户信息、配置)
@dataclass
class User:
name: str
# 空字典默认值
info: dict = field(default_factory=dict)
# 3. 集合默认值(标签、去重数据)
@dataclass
class Product:
name: str
# 空集合默认值
tags: set = field(default_factory=set)
# 4. 自定义默认值(默认带初始数据的列表)
def default_scores():
return [0, 0, 0] # 初始3个0分
@dataclass
class StudentCustom:
name: str
scores: list = field(default_factory=default_scores)
# 5. 组合使用(同时有列表+字典+集合)
@dataclass
class Combo:
name: str
my_list: list = field(default_factory=list)
my_dict: dict = field(default_factory=dict)
my_set: set = field(default_factory=set)
# 测试所有例子
if __name__ == '__main__':
# 1 测试列表
s = Student("小明")
s.scores.append(99)
print("学生分数:", s)
# 2 测试字典
u = User("小红")
u.info["age"] = 18
print("用户信息:", u)
# 3 测试集合
p = Product("电脑")
p.tags.add("电竞")
print("商品标签:", p)
# 4 测试自定义默认值
s2 = StudentCustom("小刚")
print("初始分数:", s2)
# 5 测试组合
c = Combo("测试")
print("组合数据:", c)
运行结果
学生分数: Student(name='小明', scores=[99])
用户信息: User(name='小红', info={'age': 18})
商品标签: Product(name='电脑', tags={'电竞'})
初始分数: StudentCustom(name='小刚', scores=[0, 0, 0])
组合数据: Combo(name='测试', my_list=[], my_dict={}, my_set=set())