用 Python 建立机械臂任务领域模型-领域模型(上):dataclass / Enum / 类型注解 / 不可变2026.8.10
目标: PickAndPlaceTask 能表达"红色方块→左侧箱、最多重试 2 次",且类型安全、不可变。
学习dataclass装饰器(默认值/frozen/field)
2.该模块提供装饰功能和自动功能 为用户自定义类添加生成的特殊方法,如和。最初描述了它 在PEP 557中。
这些生成方法中要使用的成员变量定义了 使用PEP 526类型的注释。例如,这段代码:
from dataclasses import dataclass
@dataclass
class InventoryItem:
"""Class for keeping track of an item in inventory."""
name: str
unit_price: float
quantity_on_hand: int = 0
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
1.@dataclass装饰器自动生成__init__构造函数 下方手写的__init__,等价于装饰器自动帮我们生成的代码,不需要手动写在类里面。
def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0):
self.name = name
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand
2.字段语法:
2.1 name : str 、unit_price : float :没有默认值,实例化必须传入
2.2 quantity_on_hand : int = 0 :设置默认值,不传参自动等于0
3 实例方法 total_cost()
计算库存总价值:单价 × 在库数量。
本地测试:

模块内容
@数据类。dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=假,冻结=假,match_args=真,kw_only=假,slots=假,weakref_slot=False)
装饰师检查了该类别以寻找S。A 被定义为具有类型注释的类变量。有两个 下文描述的例外,中没有任何内容检查变量注释中指定的类型。@dataclassfieldfield@dataclass
所有生成方法中场的顺序为 它们在类定义中的出现顺序。
装饰师会添加各种“dunder”方法。 以下将介绍的类别。如果已经有添加的某些方法 存在于该类中,行为依赖于参数,如文档所述 如下。装饰器返回与其被调用的同一类别;没有新消息 类别被创建。@dataclass
如果仅作为无参数的简单装饰器使用, 它表现得好像这里有记录的默认值 签名。也就是说,这三种用法是 等价:@dataclass@dataclass
@dataclass
class C:
...
@dataclass()
class C:
...
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False,
match_args=True, kw_only=False, slots=False, weakref_slot=False)
class C:
...
参数如下:@dataclass
1. init: bool = True
- 为
True:自动生成__init__()构造方法,把类字段作为入参初始化实例。 - 如果手写了
__init__(),本参数直接被忽略,不会自动生成构造函数。
2. repr: bool = True
- 为
True:自动生成__repr__(),输出格式:类名(字段1=值1, 字段2=值2,...),按代码定义字段顺序输出。 - 标记
repr=False的字段不会出现在打印字符串中。 - 手写
__repr__()则该参数失效。
示例输出:
InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10)
3. eq: bool = True
- 为
True:自动生成__eq__(),用于==对象相等比较。 - Python3.13 行为:逐个字段分别比较
self.a == other.a and self.b == other.b。 - 3.12 及更早版本:把所有字段打包成元组再比较
(self.a,self.b) == (other.a, other.b)。
⚠️ 3.13 变更风险:遇到
float("nan")这类自身不等于自身的值,比较结果会和旧版本不一样。
- 只能对比同类型实例;手写
__eq__()则参数失效。
4. order: bool = False
- 为
True:自动生成全套比较魔术方法:__lt__/__le__/__gt__/__ge__,支持< <= > >=。 - 比较逻辑:把实例字段打包元组,按元组规则对比;只能同类型对象对比。
- 约束:
order=True时,如果eq=False,会强制把eq提升为True。 - 如果手动写了任意一个大小比较魔术方法,直接抛异常,不允许混用。
5. unsafe_hash: bool = False
控制是否自动生成 __hash__(),用于字典 key、set 集合。
对象可哈希要求实例逻辑不可变;可变对象不应该有 hash。
dataclass 自动生成 hash 的默认规则
eq=True && frozen=True:自动生成__hash__eq=True && frozen=False:设置__hash__ = None,对象不可哈希(可变对象)eq=False:不干预__hash__,继承父类 hash(默认是对象 id 哈希)
unsafe_hash=True:强制生成__hash__,哪怕对象是可变的。属于高级危险用法:对象字段修改后 hash 值会变,放到 dict/set 会彻底错乱。- 如果你自己手写了
__hash__,不能同时开unsafe_hash=True,会报错。
6. frozen: bool = False
frozen=True:冻结实例,实例创建完成后禁止修改实例字段,赋值直接抛异常,模拟只读对象。- 如果自己手写
__setattr__/__delattr__,再设置frozen=True会抛出异常。
开启
frozen=True+eq=True→ dataclass 会自动生成__hash__,实例可以放进 set、作为字典 key。
7. match_args: bool = True(Python3.10 新增)
自动生成 __match_args__ 元组,用于模式匹配 match‑case 语法。
- 保存
__init__中非关键字 - only 的形参名。 match_args=False,或者手写__match_args__,不会自动生成。
8. kw_only: bool = False(3.10 新增)
kw_only=True:所有字段变成仅关键字参数。实例化的时候,必须用关键字传参,不能按位置传参。
python
运行
@dataclass(kw_only=True)
class A:
x:int
# 合法:A(x=10)
# 非法:A(10)
kw_only 字段不会加入
__match_args__。
关键记忆总结表
表格
| 参数 | 默认值 | 核心作用 |
|---|---|---|
| init | True | 自动生成构造__init__ |
| repr | True | 自动生成打印__repr__ |
| eq | True | 自动生成相等判断__eq__ |
| order | False | 生成全套大小比较运算符 |
| frozen | False | 冻结对象,禁止修改属性 |
| unsafe_hash | False | 强制生成 hash,可变对象慎用 |
| match_args | True | 支持 match‑case 模式匹配 |
| kw_only | False | 全部字段强制关键字传参 |
fields 可以选择性地指定默认值,使用normal Python语法:
@dataclass
class C :
a : int
b : int = 0
在本例中,和都将包含在新增的方法中,该方法定义为:a b
def __init__(self, a: int, b: int = 0):
如果字段没有默认值,则 将被提升 跟随一个带有默认值的字段。无论如何,这都成立 发生在单个类中,或因类继承而发生。
数据类field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING, doc=None)
对于常见且简单的使用场景,其他功能都没有 必须。然而,存在一些数据类特征 需要额外的各领域信息。以满足这一需求 补充信息,你可以替换默认字段值 调用提供的函数。例如:field()
@dataclass
class C:
mylist: list[int] = field(default_factory=list)
c = C()
c.mylist += [1, 2, 3]
如上所示,该值是一个哨兵对象,用于 检测用户是否提供某些参数。这个哨兵是 使用是因为对于某些参数,且 是有效值。 一个明确的含义。没有代码应该直接使用该值。NoneMISSING
参数如下:field()
default
作用:给字段写死一个固定默认值
- 实例创建的时候,不填这个参数,就自动用这个值。
- ⚠️不要给列表、字典这种可变东西用这个!所有对象会共用同一个列表,改一个全部都变。
age: int = field(default=18)
不传 age 就自动是 18。
default_factory
作用:动态生成默认值,专门对付列表、字典这类可变数据
- 必须传一个不带参数的函数,创建对象的时候,调用这个函数生成默认值。
- ❗不能和 default 一起写,二选一。
tags: list = field(default_factory=list)
每新建一个对象,就调用一次 list (),给你生成全新空列表,各个实例互不干扰。
init
默认 True
init=True:这个字段会出现在构造函数__init__的参数里,创建对象的时候可以传值。init=False:构造函数不让传这个字段,内部自己用,代码里手动赋值(一般在__post_init__里面赋值)。
要不要在创建对象的时候给这个变量传参。
uuid: str = field(init=False)
def __post_init__(self):
self.uuid = "自动生成一串编号"
repr
默认 True
repr=True:打印对象的时候,会显示这个字段的值。repr=False:打印对象的时候隐藏这个字段。适合密码、密钥。
打印这个类实例的时候,要不要把这个值显示出来。
password: str = field(repr=False)
hash
默认 None,可选 True / False / None 对象放到 set、做字典 key 的时候,要计算 hash 值。
None:跟着 compare 参数走。参与比较就参与哈希,不参与比较就不参与哈希(绝大多数情况用默认)True:强制这个字段参与哈希计算False:比较相等要看这个字段,但是算哈希的时候不带它
正常不用改。特殊场景:这个字段对比要用到,但是算哈希特别慢,就设置
hash=False。 注意:这个参数乱改容易出 bug。
compare
默认 True 控制 ==、>、< 对比两个对象的时候,要不要拿这个字段做对比。
compare=True:对比两个对象是否相等,要看这个字段。compare=False:对比相等直接忽略这个字段。
两个对象用 == 判断相等的时候,要不要比较这个变量。
metadata
默认 None 可以塞一个字典。
dataclass 本身完全不看这个数据,存着给别的第三方库读取。比如校验库、序列化库。自己业务代码想存点附加信息放这里。
username: str = field(metadata={"注释":"登录账号","最大长度":20})
kw_only (python3.10 新增)
默认 False
kw_only=True:创建对象的时候,这个参数只能关键字传参,不能位置传参。
调用的时候只能写
obj(b=xxx),不能直接obj(100)。
@dataclass
class Demo:
a:int
b:int = field(kw_only=True)
Demo(10, b=20) # ✅可以
# Demo(10,20) ❌报错,b只能关键字传入
doc(python3.14 新增)
给字段写注释文档,IDE 可以识别这个字段说明。
name:str = field(doc="用户真实姓名")
class dataclasses.Field
Field 对象
⚠️ 你千万不要自己手动 new Field () 对象! Field 对象是 Python 内部自动创建出来的。通过
dataclasses.fields()函数才能拿到它。
Field 对象公开属性:
name:字段名字type:字段标注的类型default、default_factory、init、repr、hash、compare、metadata、kw_only和你调用field(...)传入的参数完全一样,存的就是那些值。
还有别的内部属性,但属于私有实现,业务代码不要读取、不要依赖,版本升级可能直接变。
class dataclasses.InitVar[T]
只用于构造函数的伪字段
用 InitVar[int] 写的变量,不是真正实例属性,叫「伪字段」:
- 只会加到
__init__、__post_init__的参数里; fields()获取字段列表时不会返回它;- 对象实例上不会生成这个成员变量。
👉 使用场景:有些参数只在创建对象的时候用一下,不需要保存到实例身上。
from dataclasses import dataclass, InitVar
@dataclass
class User:
name: str
raw_pwd: InitVar[str] # 仅初始化时传入,不会变成 self.raw_pwd
def __post_init__(self, raw_pwd):
# raw_pwd 只在这里能用,实例没有这个属性
self.pwd_hash = raw_pwd + "_hash"
创建对象要传 raw_pwd,但是 user.raw_pwd 访问会报错。
dataclasses.fields(class_or_instance)
拿到数据类所有真实字段的元组
入参:可以传数据类本身,或者该类的实例对象。 返回:一堆 Field 对象组成的元组。
⚠️ 不会返回伪字段:ClassVar、InitVar。 如果传入的不是 dataclass,直接抛 TypeError。
python
运行
from dataclasses import fields
fs = fields(User)
for f in fs:
print(f.name, f.type)
查看这个 dataclass 有哪些真正的字段,做序列化、遍历字段的时候经常用。
dataclasses.asdict(obj, *, dict_factory=dict)
把 dataclass 对象转成字典。
规则:
- 递归转换:嵌套 dataclass、list、tuple、dict 都会递归展开;
- 其他类型对象,底层用
copy.deepcopy()拷贝; - 返回
{字段名:值}的字典。
from dataclasses import dataclass, asdict
@dataclass
class Point:
x: int
y: int
@dataclass
class C:
mylist: list[Point]
p = Point(10, 20)
print(asdict(p)) # {'x': 10, 'y': 20}
c = C([Point(0, 0), Point(10, 4)])
print(asdict(c))
# {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}
默认是深拷贝,性能开销大。 如果想要浅拷贝版本,自己写推导:
{field.name: getattr(obj, field.name) for field in fields(obj)}
如果传进来不是 dataclass 实例,抛出 TypeError。
dataclasses.astuple(obj, *, tuple_factory=tuple)
把 dataclass 对象转元组。
规则和 asdict 几乎一致:嵌套 dataclass、列表、元组递归展开;其余对象执行深拷贝。
from dataclasses import astuple
p = Point(10, 20)
print(astuple(p)) # (10, 20)
浅拷贝手写版本:
tuple(getattr(obj, field.name) for field in fields(obj))
不是 dataclass 实例,抛 TypeError。
ataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, ...)
动态创建 dataclass,不用写
class xxx:代码,运行时拼出一个数据类。
参数简要通俗说明:
cls_name:新类的类名字符串fields:字段列表,每一项可以是三种格式:'name'只写名字,类型默认Any('name', 类型)名字 + 类型('name', 类型, field(...))名字 + 类型 + field 配置对象
bases=():父类元组,继承哪些类namespace=None:类命名空间,放方法、类变量(字典)- 后面一堆开关
init,repr,eq,order,frozen...和@dataclass装饰器参数一模一样。 module:设置类的__module__属性;默认取调用处的模块名。decorator(Python3.14 新增):用哪个装饰器来生成 dataclass,默认就是@dataclass。
dataclasses.replace(obj, /, **changes)
核心作用
拿一个 dataclass 实例,生成一份全新的对象,把你传进去的字段覆盖成新值,原有对象完全不变。
⚠️不是原地修改旧对象,是返回新副本。
官方规则
-
obj必须是 dataclass 实例 如果传普通类对象,直接抛TypeError。 -
**changes只能传这个数据类存在的字段名 写了不存在的字段名 →TypeError报错。
new_obj = replace(old_obj, name="张三", age=20) # ok
new_obj = replace(old_obj, xxx=999) # xxx不是类字段 → TypeError
- 内部会调用类的
__init__不是简单复制属性;会走完整初始化流程,__post_init__也会被执行。
这点很关键:不是浅拷贝赋值,相当于拿旧实例大部分值 + 你给的新参数,重新
__init__造一个新对象。
-
InitVar 仅初始化变量要注意 如果类里有不带默认值的
InitVar[T],调用replace()的时候必须手动传这个参数,不然__init__ / __post_init__拿不到值会报错。 -
不能修改
init=False的字段 用field(init=False)定义的字段,不能在replace()里赋值。
因为这些字段不在
__init__入参列表,传进去直接抛ValueError。init=False代表:不允许构造函数设置该字段,replace()底层调用__init__,自然也不让改。
3.init=False字段在 replace 时不会自动复制旧实例的值
大坑!
replace()不会把旧对象身上init=False的字段拷贝到新对象。 新对象的init=False字段会走类本身逻辑,大多情况变成未初始化。
官方建议:尽量少用
init=False;如果非要用,不要依赖replace(),自己手写拷贝方法。
- 补充:标准库
copy.replace()泛型函数也支持 dataclass 对象。
示例:
from dataclasses import dataclass,replace,field,InitVar
@dataclass
class User:
name: str
age: int
#init = False :构造函数不能传入,一般在_post_init_赋值
secret: str = field(init=False)
token: InitVar[str]
def __post_init__(self, token: str):
self.secret = f'sec_{token}'
print("原对象")
u1 = User('Tom', 18, '123456')
print(u1)
print("-------------------------------------------------------")
print("替换对象")
#替换普通字段,initvar字段必须显示传入
u2 = replace(u1, age=20, token='abcdefg')
print(u2)
print("-------------------------------------------------------")
print("源对象未改变")
print(u1)
print("-------------------------------------------------------")
print("_post_init_重新运行,secret被重新生成")
print(u2.secret)

错误1:# ❌错误:尝试修改 init=False 的secret字段
print("# 错误:尝试修改 init=False 的secret字段")
u3 = replace(u1, secret='abcdefg')
print(u3)
运行结果:

错误2:❌错误:漏掉 InitVar 参数
print("漏掉 InitVar 参数")
u4 = replace(u1, age=20)
print(u4)
运行结果:__init__缺少token,直接抛异常

注意:
replace()不改原对象,必须接收返回值;init=False字段不要指望replace帮你复制,会丢失;- 使用
InitVar不带默认值,每次replace都要再传一遍;
dataclasses.is_dataclass(obj)
官方语义
返回布尔值:
True:传入的是dataclass 类本身,或者是 dataclass 的实例对象,子类也返回 True;泛型别名不算。False:普通类、普通实例返回 False。
重点坑: is_dataclass() 分不清你传的是「类」还是「类的对象」
例如:
from dataclasses import dataclass, is_dataclass
@dataclass
class Demo:
x:int
print(is_dataclass(Demo)) # True → 传的是类本身
print(is_dataclass(Demo(10))) # True → 传的是实例对象
验证:

想要只判断是不是实例对象(排除类本身),就加上判断 not isinstance(obj, type):
def is_dataclass_instance(obj):
# is_dataclass为真,并且obj不是一个类(type),才是实例
return is_dataclass(obj) and not isinstance(obj, type)
print(is_dataclass_instance(Demo)) # False,这是类
print(is_dataclass_instance(Demo(10))) # True,这是实例
运行结果:
拆解逻辑
-
isinstance(obj, type):如果 obj 是类,返回 True;对象实例返回 False。
-
Demo是类 →isinstance(Demo, type)→ True -
Demo(10)是实例 →isinstance(Demo(10), type)→ False
所以: is_dataclass(obj) and not isinstance(obj, type) 👉 “是 dataclass,并且它不是一个类” → 就一定是 dataclass 的实例。
记忆口诀
-
is_dataclass():类、实例都返回 True,一锅端; -
要只判断实例:必须叠加
not isinstance(obj, type)。
dataclasses.MISSING
官方描述:MISSING 是一个哨兵标记值,用来代表:没有设置 default,也没有设置 default_factory。
通俗理解
MISSING 不是你要传给代码的参数,是 dataclasses 内部用的占位标记。
- 当你写
field(),既没写default=xxx,也没写default_factory=...,那么这个字段的.default属性就等于MISSING。 - 它的含义:此字段没有默认值,实例化时必须手动传参,不能省略。
注意:不要把
MISSING和None搞混。
None:是合法默认值,代表 “默认就是空”MISSING:代表 “压根就没提供默认,必须外部传入”
from dataclasses import dataclass, field, fields, MISSING
@dataclass
class Demo:
a: int # 无默认
b: int = 10 # default=10
c: int = field(default_factory=lambda: 20)
d: int | None = None # default=None
flds = fields(Demo)
for f in flds:
print(f.name, "default=", repr(f.default), "is MISSING?", f.default is MISSING)
a:没有默认 →f.default is MISSING→ 创建对象必须传ab:有字面默认值,default=10c:用了default_factory,此时f.default依然是MISSING,真正的生成逻辑交给工厂函数。
关键点:只要使用
default_factory,field.default永远等于MISSING。
什么时候会见到 MISSING
写工具、反射遍历 dataclass 字段时会用到:
for f in fields(obj):
if f.default is MISSING and f.default_factory is MISSING:
print(f"字段 {f.name} 没有任何默认值,__init__必须传入")
三个状态区分
default = MISSING,default_factory = MISSING:无默认,必须传参default = 某个值:使用普通默认值default_factory = 可调用对象:运行时调用工厂生成默认值
dataclasses.KW_ONLY
作用:伪字段标记,放在类的注解里,KW_ONLY 后面定义的所有字段,在__init__实例化时只能用关键字传参,不能用位置传参。
_: KW_ONLY这一行本身是伪字段:不会生成真正的类成员,名字随便写,惯例用下划线_。- 出现在
KW_ONLY之后的所有字段:强制关键字参数; - 一个 dataclass 里面只能出现一次 KW_ONLY,写多个直接报错。
示例拆解:
from dataclasses import dataclass, KW_ONLY
@dataclass
class Point:
x: float
_: KW_ONLY # ✨分界线:后面全部强制关键字传参
y: float
z: float
p = Point(0, y=1.5, z=2.0) # 正确:x位置传;y,z必须关键字
# p = Point(0, 1.5, 2.0) # 报错!y、z不允许位置传参
输出结果:
-
x在 KW_ONLY 前面:既可以位置传,也可以关键字传 -
y、z在 KW_ONLY 后面:只能关键字传参
伪字段本身完全被忽略:不会出现在
fields()、不会生成实例属性,只做分割标记。
exception dataclasses.FrozenInstanceError
当 dataclass 设置 frozen=True,这个类的实例变成冻结对象,不可修改。
试图 obj.a = xxx 赋值 或者 del obj.a 删除属性时,抛出该异常。
继承自 AttributeError。
示例:
from dataclasses import dataclass
@dataclass(frozen=True)
class User:
name: str
age: int
u = User("zhangsan", 20)
# u.age = 99 # 抛出 FrozenInstanceError,不能修改
# del u.name # 同样抛出 FrozenInstanceError

小知识点: frozen=True 对象虽然不能直接赋值,但可以用 replace() 返回新对象实现 “修改”。
from dataclasses import replace
u2 = replace(u, age=99) # ✅生成新实例,原对象不变
frozen 注意点
frozen=True只是拦截__setattr__/__delattr__;- 如果字段是可变对象(list/dict),对象内部元素依然可以改:
@dataclass(frozen=True) class Demo: arr: list d = Demo([1,2]) d.arr.append(3) # ✅不会报错!只是不能替换arr本身,列表内部可变输出结果:

快速速记
- KW_ONLY(3.10+)
- 伪分割标记,后面字段强制关键字传参;
- 一个类最多写 1 次;
- 惯例命名
_: KW_ONLY,本身不产生字段。
- FrozenInstanceError
frozen=True的实例被写 / 删除属性时抛出;- 想 “改值” 用
replace()生成新对象; - 冻结挡不住可变容器内部修改。
dataclasses 完整 API 精简速查表
装饰器
表格
| API | 作用 |
|---|---|
@dataclass() | 自动生成 • • • • • |
field () 字段配置
from dataclasses import field
field(
default=..., # 字段普通默认值;和default_factory互斥
default_factory=...,# 零参数可调用对象,运行时生成可变默认(list/dict)
init=True, # 是否加入 __init__ 参数;init=False 不能用replace修改
repr=True, # 是否放进repr输出
hash=None, # 是否参与hash计算;None跟随compare
compare=True, # 是否参与 == > < 比较
metadata={}, # 附加自定义元数据字典,不参与运行逻辑
kw_only=False # 该字段单独强制关键字传参(3.10+)
)
运行时工具函数
| 函数 | 功能 | 关键点 |
|---|---|---|
fields(obj) | 返回字段 Field 对象元组 | 支持类 / 实例;普通对象抛异常 |
is_dataclass(obj) | 是否 dataclass(类或者实例都返回 True) | 不能区分类 / 实例;判断实例:is_dataclass(obj) and not isinstance(obj,type) |
replace(obj,**changes) | 基于旧实例生成全新 dataclass 对象 | 底层调用__init__+__post_init__❌不能修改init=False字段;不会复制 init=False 旧值❌InitVar无默认必须手动传入 |
asdict(obj) | dataclass 实例转为字典 | 递归转换嵌套 dataclass |
astuple(obj) | dataclass 实例转为元组 | 递归转换嵌套 dataclass |
make_dataclass() | 动态创建 dataclass 类 | 运行时动态造类,极少使用 |
特殊类型 / 哨兵常量
| 名称 | 说明 |
|---|---|
InitVar[T] | 仅初始化伪字段,只传给__init__、__post_init__不会成为实例属性;replace()必须显式传入无默认 InitVar |
KW_ONLY(3.10+) | 伪字段分割标记;其后所有字段强制关键字传参惯例写法:_: KW_ONLY;一个类只能出现一次 |
MISSING | 内部哨兵;代表没有 default、没有 default_factory⚠️不等于None;业务代码不要拿来赋值 |
异常
| 异常 | 触发场景 |
|---|---|
FrozenInstanceError | frozen=True,执行obj.x=val / del obj.x;继承AttributeError |
Field 对象属性(fields()返回)
f = fields(obj)[0]
f.name # 字段名字
f.type # 字段类型注解
f.default # 默认值,无默认则=MISSING
f.default_factory
f.init/repr/hash/compare/metadata/kw_only
高频踩坑清单
replace()返回新对象,不会修改原对象;init=False字段不会拷贝旧值frozen=True仅拦截属性赋值,list/dict 内部元素依旧可变default_factory只能传无参函数,不能传 lambda 带参数is_dataclass()类、实例均返回 True,判断实例必须加not isinstance(obj,type)InitVar不会变成实例成员,只在初始化阶段生效MISSING≠None,MISSING 代表完全没有配置默认KW_ONLY只是分割标记,本身不生成实例属性
enum枚举
枚举:
-
是一组符号名称(成员),绑定于唯一的值
-
可以迭代以返回其典范(即非别名)成员 定义顺序
-
使用调用语法按值返回成员
-
使用索引语法按名称返回成员
枚举可以通过语法创建,或通过 使用函数调用语法:
from enum import Enum
# class syntax
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# functional syntax
Color = Enum('Color', [('RED', 1), ('GREEN', 2), ('BLUE', 3)])
Enum
所有枚举的基类
实例属性(每个枚举成员身上)
1. .name
枚举成员定义时的名字。
Color.BLUE.name
# 'BLUE'
2. .value
枚举成员绑定的值。
Color.RED.value
# 1
可以在
__new__()方法里面修改这个值。
枚举成员的值可以是 int、str 任意类型。如果不在乎具体数值,可以用
auto(),让系统自动帮你生成值。虽然可以用 dict、list 这类可变、不可哈希对象作为枚举值,但创建枚举的时候性能会急剧变差(平方级耗时),尽量不要用可变对象做枚举值。
3. _name_: 内部属性,等价于 .name
4. _value_: 内部属性,等价于.value
5. _order_(已废弃,仅为向前兼容保留)
Python2 时代产物,Python3 不再需要。 用来强制校验枚举定义顺序和字符串列表是否一致,不一致直接抛 TypeError。
示例:
from enum import Enum
class Color(Enum):
_order_ = 'RED GREEN BLUE'
RED = 1
BLUE = 3
GREEN = 2
报错:定义顺序和_order_写的顺序对不上

6._ignore_
写一个名字列表,列表里的名字不会变成枚举成员,类构建完成后这些名字直接从枚举类删掉。 用于把辅助变量、工具函数排除掉,不让它们变成枚举项。
特殊魔术方法/钩子方法
_dir_(self)
调用 dir(枚举成员实例) 返回的属性列表。 默认包含:name、value,以及你写在枚举类上的类方法。
示例:你写了
@classmethod today(),dir(Weekday.SATURDAY)就能看到today。
_generate_next_value_(name, start, count, last_values) 【静态方法】
配合 auto() 使用,自定义 auto 自动生成的值。 参数:
name:当前正在定义的枚举成员名字start:起始值,默认 1count:当前已经定义了多少个成员(不包含当前这一个)last_values:前面所有已经生成过的值列表
默认行为:
- 普通
Enum:在上一个最大值基础上 +1 Flag枚举:生成 2 的幂次(1,2,4,8…)
自定义示例:每次返回 3 的 n 次方
from enum import auto, Enum
class PowersOfThree(Enum):
@staticmethod
def _generate_next_value_(name, start, count, last_values):
return 3 ** (count + 1)
FIRST = auto()
SECOND = auto()
print(PowersOfThree.SECOND.value) # 9
_init_(self,*args,**kwds)
默认什么都不干
如果枚举赋值写了多个值,会全部传给_init_.
class Weekday(Enum):
MONDAY = 1, 'Mon'
# 等价调用 __init__(self, 1, "Mon")
_init_subclass_(cls,**kwds)
子类钩子,继承Enum的子类创建时触发,默认不做事情,可以重写做自定义配置。
_missing_(cls,value)
按值查找枚举找不到的时候,会调用这个钩子,用来实现自定义匹配逻辑。
比如忽略大小写、模糊匹配。返回找到的枚举成员;找不到返回
None。
示例:字符串枚举忽略大小写查找
from enum import auto, StrEnum
class Build(StrEnum):
DEBUG = auto()
OPTIMIZED = auto()
@classmethod
def _missing_(cls, value):
value = value.lower()
for member in cls:
if member.value == value:
return member
return None
print(Build("deBUG")) # <Build.DEBUG: 'debug'>
输出结果:

_new_(cls,*args,**kwds)
默认不存在。如果你重写它,赋值的全部参数都会传入。
注意:自定义
__new__不要写super().__new__(),直接调用对应类型的__new__。 比如继承int, Enum,就调用int.__new__(...)。
_repr_(self)
控制repr(枚举实例) 的输出。默认输出<类名.成员名:value>,可以重写。
_str_(self)
控制str(枚举实例)的输出。默认输出类名.成员名,可重写。
_format_(self)
控制f-string/format()的格式化输出,默认调用_str_,可以重写。
_add_alias(name)
给已存在的枚举成员增加别名。同一个枚举值,多个名字。
Color.RED._add_alias_("ERROR")
Color.ERROR # 等价于 Color.RED
如果这个名字已经被别的成员占用,抛出NameError
_add_value_alias_(value)
给已有的枚举成员增加值别名。用这个新值也能查到该枚举。
Color.RED._add_value_alias_(42)
Color(42) # 返回 Color.RED
Enum 极简速查小卡片
| 项目 | 说明 |
|---|---|
.name | 枚举成员的名字字符串 |
.value | 枚举成员绑定的值 |
auto() | 自动生成值,默认从 1 开始自增 |
_generate_next_value_ | 重写这个静态方法,自定义 auto 生成规则 |
_ignore_ | 名字列表,列表内的名字不生成枚举成员 |
_missing_ | 按值查找失败时触发,做模糊 / 兼容查找 |
__new__ | 控制枚举实例创建,处理传入的赋值参数 |
__init__ | 枚举成员初始化,多值赋值会拆成多个参数传入 |
_add_alias_() | 3.13+,增加名字别名 |
_add_value_alias_() | 3.13+,增加值别名 |
IntEnum
IntEnum 继承 Enum,枚举成员同时也是整数,可以直接参与整数运算;运算之后得到普通 int,不再是枚举对象。
from enum import IntEnum
class Number(IntEnum):
ONE = 1
TWO = 2
THREE = 3
关键特性演示:
# 原始枚举成员,保留枚举身份
print(Number.THREE) # Number.THREE
print(repr(Number.THREE)) # <Number.THREE: 3>
# ✅可以直接做整数加减运算
print(Number.ONE + Number.TWO) # 3 返回普通int,不再是Number枚举
print(Number.THREE + 5) # 8
# ✅直接和数字做相等比较
print(Number.THREE == 3) # True
# ⚠️运算结果丢失枚举类型
res = Number.ONE + Number.TWO
print(type(res)) # <class 'int'>,不是Number
输出结果:

和普通Enum核心区别
from enum import Enum
class NumEnum(Enum):
ONE =1
print(NumEnum.ONE == 1) # False 普通Enum不等于数字
print(Number.ONE ==1) # True IntEnum等于数字
auto()在IntEnum
使用auto()自动赋值,从1开始自增整数
from enum import IntEnum, auto
class AutoNum(IntEnum):
A = auto() # 1
B = auto() # 2
C = auto() # 3
IntEnum.__str__()改成和int.__str__()行为一致,str(Number.THREE)得到字符串"3"__format__()同样对齐 int,目的:更好替代项目里零散数字常量。
# 3.11+行为
str(Number.THREE) # '3' 老版本输出 'Number.THREE'
f"{Number.THREE}" # '3'
StrEnum
StrEnum 是字符串枚举(Python 3.11 + 新增),成员既是枚举实例,同时又是字符串,可以直接参与字符串操作;但字符串运算后的返回值是普通字符串,不再属于枚举对象。
from enum import StrEnum, auto
class Color(StrEnum):
RED = 'r'
GREEN = 'g'
BLUE = 'b'
UNKNOWN = auto()
运行示例:
print(Color.RED) # Color.RED
print(repr(Color.RED)) # <Color.RED: 'r'>
# auto():值为**成员名字小写字符串**
print(Color.UNKNOWN) # <Color.UNKNOWN: 'unknown'>
print(str(Color.UNKNOWN)) # 'unknown'
# 可以直接做字符串运算,运算结果变成普通str,丢失枚举身份
s = Color.RED + "_color"
print(s) # r_color
print(type(s)) # <class 'str'>,不再是Color枚举
输出结果:

auto()行为重点
StrEnum 使用 auto(),自动取值为枚举成员的小写
class Demo(StrEnum):
HELLO = auto() # 值 = "hello"
TEST_DATA = auto() # 值 = "test_data"
设计改动
__str__、__format__对齐原生str的行为str(Color.RED)直接拿到字符串'r',目的:用来替换项目里零散硬编码字符串常量。
非常重要的坑
部分标准库代码写的是严格类型判断:type(x) == str,而不是 isinstance(x, str)。 StrEnum 的成员是str的子类,这种严格相等校验会失败。
解决:手动转字符串
# 遇到type(x) == str校验的地方,强制str()包裹
some_func(str(Color.RED))
和普通Enum对比
from enum import Enum
class ColorNormal(Enum):
RED = "r"
print(ColorNormal.RED == "r") # False 普通Enum不等于字符串
print(Color.RED == "r") # True StrEnum可以直接和字符串相等
适用场景
API 字符串状态、命令字符串、请求类型、协议字符串常量。
class ApiStatus(StrEnum):
SUCCESS = auto()
FAIL = auto()
PENDING = auto()
# 直接可以放到json、header,比较字符串
resp = {"status": ApiStatus.SUCCESS}
auto()
auto() 用来代替手写枚举值,枚举框架会调用 _generate_next_value_() 自动生成对应值,不同枚举类型生成规则不一样。
各枚举类型auto()默认规则
1.Enum/IntEnum:上一个值 +1
from enum import Enum, IntEnum, auto
class E(IntEnum):
A = auto() # 1
B = auto() # 2
C = 10
D = auto() # 11
输出结果:

2. Flag / IntFlag: 取大于当前最大值的 2的幂(位运算)
from enum import IntFlag, auto
class F(IntFlag):
A = auto() # 1 (0b001)
B = auto() # 2 (0b010)
C = auto() # 4 (0b100)
输出结果:

3. StrEnum: 把成员名字转小写字符串
from enum import StrEnum, auto
class S(StrEnum):
HELLO = auto() # "hello"
TEST_DATA = auto() # "test_data"
输出结果:

风险:手动写的值和 auto() 混用要小心,auto 会顺着上一个实际值往后算,容易出现意料之外的值。
auto()生效条件(重点)
auto()只有直接出现在赋值顶层才会被解析。
可以正常工作
FIRST = auto() # 直接赋值auto()
SECOND = auto(), -2 # 元组,auto在顶层,会被解析
不生效,不会自动生成值
THREE = [auto(), -3] # 放到列表里面,auto不会被展开,存的是原始auto对象
自定义 auto 的生成逻辑:重写 _generate_next_value_
可以覆写这个类方法,自己控制 auto 产出什么值。
from enum import Enum, auto
class MyEnum(Enum):
@staticmethod
def _generate_next_value_(name, start, count, last_values):
# name:枚举成员名字字符串
# start:起始值
# count:已经定义多少个成员
# last_values:前面所有成员的值列表
return f"prefix_{name.lower()}"
AAA = auto()
BBB = auto()
print(MyEnum.AAA.value) # prefix_aaa
print(MyEnum.BBB.value) # prefix_bbb
输出结果:

成员遍历
1.直接遍历枚举类本身(最常用)
直接for循环枚举类对象,迭代所有枚举成员实例
from enum import IntEnum, StrEnum, auto
class Color(IntEnum):
RED = 1
GREEN = 2
BLUE = 3
# 遍历成员对象
for member in Color:
print(f"name={member.name}, value={member.value}")
输出结果:

遍历得到的是枚举实例 Color.RED,不是数字 / 字符串。
2.list()转列表
members = list(Color)
print(members)
# [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 3>]
输出结果:
![]()
3_members_属性(字典,包含别名)
Enum.__members__ 返回有序字典:{成员名字符串: 枚举实例}
__members__会把别名也一并返回;普通 for 遍历会跳过别名。
for name, member in Color.__members__.items():
print(name, member.value)
别名演示:
class Color(IntEnum):
RED = 1
R = 1 # 这是RED的别名
print(list(Color)) # 只输出 RED,别名R被跳过
print(list(Color.__members__.items())) # RED、R全部拿到
输出结果:
✅普通for 枚举类:只遍历真正成员,过滤别名
✅__members__:拿到全部,包含别名,适合做反向查找
4.StrEnum遍历示例:
from enum import StrEnum, auto
class Status(StrEnum):
OK = auto()
FAIL = auto()
for m in Status:
print(m.name, m.value, str(m))
5.常用遍历衍生写法
提取全部name
names = [m.name for m in Color]
# ['RED', 'GREEN', 'BLUE']
提取全部value
values = [m.value for m in Color]
# [1,2,3]
name->menber反向查找
member = Color["RED"] # 通过名字字符串拿成员
print(member.value)
value->member反向查找
member = Color(1)
print(member.name) # RED
更多推荐




所有评论(0)