You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

枚举类型数据类:选择子类实现还是自定义构造函数?

带枚举类型的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 14:05:55