
本文详解如何在自定义 `enum` 子类中正确实现自动递增整数枚举值(类似 `auto()` 行为),同时为每个成员附加额外属性(如正则规则字符串),解决直接在 `__new__` 中调用 `auto()` 失败的问题。
在 Python 的 enum 模块中,auto() 是一个占位符对象,它仅在枚举类定义阶段被 EnumMeta 元类解析并替换为实际值,而非运行时可调用函数。因此,在 __new__ 方法内部直接写 auto()(如 member._value_ = auto())是无效的——此时 auto() 尚未被求值,且 __new__ 已处于成员实例化阶段,元类干预早已完成。
要实现“声明式赋值字符串、自动分配唯一整数 _value_、并保留原始字符串为自定义属性”的目标,核心思路是:手动模拟 auto() 的递增逻辑,并确保使用正确的基类构造方式。以下是推荐的完整实现方案:
✅ 正确做法:基于 IntEnum 的自动整数分配(推荐)
from enum import IntEnum
class TokenEnum(IntEnum):
def __new__(cls, value):
# 手动计算下一个整数值:从 1 开始递增(与 auto() 默认行为一致)
int_value = len(cls.__members__) + 1
# 关键:必须使用 int.__new__(cls, int_value) 构造整数实例
member = int.__new__(cls, int_value)
member._value_ = int_value # 显式赋值(冗余但清晰)
member.rule = value # 保存原始字符串(如正则表达式)
return member
# 使用示例
class Tokens(TokenEnum):
ID = r'[a-zA-Z_][a-zA-Z0-9_]*'
NUMBER = r'\d+'
PLUS = r'\+'
# 验证结果
print(Tokens.ID) # Tokens.ID
print(Tokens.ID.value) # 1
print(Tokens.ID.rule) # '[a-zA-Z_][a-zA-Z0-9_]*'
print(Tokens.NUMBER.value) # 2⚠️ 注意事项:必须继承 IntEnum(而非 Enum 或 object),否则无法支持整数运算和比较;构造实例时务必调用 int.__new__(cls, int_value),而非 object.__new__(cls),否则会丢失 int 类型特性;len(cls.__members__) 在 __new__ 调用时已包含当前正在创建的成员?不,它只包含此前已成功创建的成员,因此 +1 是安全且准确的起始偏移。
? 替代方案:非整数枚举但 _value_ 仍为 int(兼容性更强)
若你不需要枚举成员本身是 int 实例(例如不参与算术运算),但希望 .value 返回整数、.name 和 .rule 保持分离,可改用 Enum 基类:
from enum import Enum
class TokenEnum(Enum):
def __new__(cls, value):
int_value = len(cls.__members__) + 1
member = object.__new__(cls) # 不依赖 int 构造
member._value_ = int_value # 但 _value_ 仍是 int
member.rule = value
return member此时 Tokens.ID 是 TokenEnum 实例,Tokens.ID.value 为 1,Tokens.ID.rule 为正则字符串,语义更清晰,也避免了 IntEnum 的隐式类型约束。
立即学习“Python免费学习笔记(深入)”;
? 错误模式(务必避免)
# ❌ 错误:auto() 不能在 __new__ 中调用
def __new__(cls, value):
member = object.__new__(cls)
member._value_ = auto() # → TypeError: auto() only works inside class body
# ❌ 错误:用 object.__new__ 构造 IntEnum 成员 → 类型错误
member = object.__new__(cls) # 导致 isinstance(Tokens.ID, int) == False? 进阶提示:若只需字符串枚举 + 正则匹配?
如果枚举值本身即为正则字符串,且无需整数序号,最简洁的方式是直接继承 str, Enum:
from enum import Enum
class Tokens(str, Enum):
ID = r'[a-zA-Z_][a-zA-Z0-9_]*'
NUMBER = r'\d+'
import re
match = re.match(Tokens.ID, 'hello123') # 直接使用,无需 .rule该方式语义直观、零配置,适用于纯字符串规则场景。
综上,auto() 的不可重入性决定了我们必须手动管理序号逻辑;选择 IntEnum + int.__new__ 还是 Enum + object.__new__,取决于你对枚举实例类型的 runtime 需求。始终牢记:__new__ 是实例构造入口,而 auto 是编译期语法糖——二者不在同一抽象层级。










