枚举类型数据类:选择子类实现还是自定义构造函数?
带枚举类型的Dataclass实现方案对比
我经常需要创建包含枚举类型的dataclass,比如带有多个参数、分多种类型的Action类。目前有两种实现方式,想请教哪种方案更值得选择,或是存在更优的实现思路?
方案一:单类封装+类方法构造
这种方案将枚举和所有构造逻辑封装在单个Action类中,通过类方法提供不同类型的构造入口:
from enum import Enum, auto from dataclasses import dataclass @dataclass class Action: class Type(Enum): INVALID = 0 GET_UP = auto() GET_DOWN = auto() JUMP_AROUND = auto() user: str x: int y: int type: Type = Type.INVALID @classmethod def get_up(cls, user, x, y): return cls(user, x, y, cls.Type.GET_UP) @classmethod def get_down(cls, user, x, y): return cls(user, x, y, cls.Type.GET_DOWN) @classmethod def jump_around(cls, user, x, y): return cls(user, x, y, cls.Type.JUMP_AROUND)
使用示例
from pakidge.action import Action act = Action.jump_around("muggs", 92, 5) assert act.type == Action.Type.JUMP_AROUND assert Action.get_up("asdf", 1, 2) == Action("asdf", 1, 2, Action.Type.GET_UP)
方案二:模块级枚举+子类继承
这种方案将枚举放在模块级别,为每种Action类型创建独立子类,通过子类构造时自动绑定对应枚举值:
from enum import Enum, auto from dataclasses import dataclass, asdict class Type(Enum): INVALID = 0 GET_UP = auto() GET_DOWN = auto() JUMP_AROUND = auto() @dataclass class Action: user: str x: int y: int type: Type = Type.INVALID def __eq__(self, other): """允许基类和子类之间按值比较""" if not isinstance(other, (self.__class__, Action)): return False return asdict(self) == asdict(other) class GetUp(Action): def __init__(self, user: str, x: int, y: int): super().__init__(user, x, y, Type.GET_UP) class GetDown(Action): def __init__(self, user: str, x: int, y: int): super().__init__(user, x, y, Type.GET_DOWN) class JumpAround(Action): def __init__(self, user: str, x: int, y: int): super().__init__(user, x, y, Type.JUMP_AROUND)
使用示例
from pakidge import action act = action.JumpAround("muggs", 92, 5) assert act.type == action.Type.JUMP_AROUND assert action.GetUp("asdf", 1, 2) == action.Action("asdf", 1, 2, action.Type.GET_UP)
两种方案的优劣势对比
方案一的优缺点
- 优势:
- 封装性强,所有相关逻辑集中在一个类中,调用路径清晰
- 枚举类型与构造方法强绑定,避免手动传入错误的枚举值
- 无需额外重写
__eq__,dataclass默认的按值比较就能正常工作
- 劣势:
- 若后续每种Action需要添加专属行为(如不同的执行逻辑),所有代码会堆在一个类中,导致类臃肿
- 无法通过类型注解区分不同Action类型,静态检查工具无法提供精准提示
方案二的优缺点
- 优势:
- 每种Action是独立子类,便于扩展专属方法和属性,符合开闭原则
- 可以通过类类型直接区分Action类型,静态检查和IDE提示更精准
- 劣势:
- 需要手动重写
__eq__才能保证基类与子类、子类之间的按值比较正常工作 - 模块中类数量增多,会增加一定的认知负担
- 需要手动重写
更优实现思路:枚举绑定子类+工厂模式
结合两种方案的优点,我们可以用枚举绑定对应的子类,再通过工厂方法统一入口,既保留子类的扩展性,又保证构造逻辑的简洁:
from enum import Enum, auto from dataclasses import dataclass from typing import Union class ActionType(Enum): INVALID = auto() GET_UP = auto() GET_DOWN = auto() JUMP_AROUND = auto() @dataclass class Action: user: str x: int y: int type: ActionType = ActionType.INVALID @dataclass class GetUp(Action): type: ActionType = ActionType.GET_UP @dataclass class GetDown(Action): type: ActionType = ActionType.GET_DOWN @dataclass class JumpAround(Action): type: ActionType = ActionType.JUMP_AROUND # 工厂方法,根据枚举值返回对应子类实例 def create_action(action_type: ActionType, user: str, x: int, y: int) -> Union[Action, GetUp, GetDown, JumpAround]: type_map = { ActionType.GET_UP: GetUp, ActionType.GET_DOWN: GetDown, ActionType.JUMP_AROUND: JumpAround } cls = type_map.get(action_type, Action) return cls(user, x, y)
使用示例
act = create_action(ActionType.JUMP_AROUND, "muggs", 92, 5) assert isinstance(act, JumpAround) assert act.type == ActionType.JUMP_AROUND
这种方案的好处:
- 子类独立,便于扩展专属逻辑
- 工厂方法统一入口,避免构造错误
- dataclass自动处理
__eq__,无需手动重写 - 静态检查工具可以通过返回类型注解识别具体子类
内容的提问来源于stack exchange,提问作者Jack Deeth
相关产品推荐
相关产品推荐

