如何创建允许同义词的Python枚举?

2024-09-30 16:29:40 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在规范化现有的凌乱数据,我想创建一个Enum,它允许成员的规范名称使用同义词,这样,如果有人在实例化枚举时使用同义词值,他们将获得规范名称。即

class TrainOutcome(enum.Enum):
    PASSED = "PASSED"
    SUCCESS = "PASSED" # Deprecated synonym for "PASSED"
    FAILED = "FAILED"
    STARTED = "STARTED"

这执行得很好,但结果枚举的行为不符合预期:

>>> TrainOutcome("PASSED")
<TrainOutcome.PASSED: 'PASSED'>

# I want to get <TrainOutcome.PASSED: 'PASSED'> here as well
>>> TrainOutcome("SUCCESS")
ValueError: 'SUCCESS' is not a valid TrainOutcome

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/enum.py", line 309, in __call__
    return cls.__new__(cls, value)
  File "/usr/lib/python3.8/enum.py", line 600, in __new__
    raise exc
  File "/usr/lib/python3.8/enum.py", line 584, in __new__
    result = cls._missing_(value)
  File "/usr/lib/python3.8/enum.py", line 613, in _missing_
    raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 'SUCCESS' is not a valid TrainOutcome

尽管__members__属性似乎完全按照我所希望的方式映射事物:

>>> TrainOutcome.__members__
mappingproxy({'PASSED': <TrainOutcome.PASSED: 'PASSED'>, 'SUCCESS': <TrainOutcome.PASSED: 'PASSED'>, 'FAILED': <TrainOutcome.FAILED: 'FAILED'>, 'STARTED': <TrainOutcome.STARTED: 'STARTED'>})
>>> TrainOutcome['SUCCESS']
<TrainOutcome.PASSED: 'PASSED'>
>>> TrainOutcome['PASSED']
<TrainOutcome.PASSED: 'PASSED'>

如何创建枚举,使构造函数接受并返回与索引类型相同的值

Edit:现有的Python Enum with duplicate values没有回答我的问题,因为本质上它试图实现与我所追求的相反的目标。OP想要使结果值更清晰,我想要使它们不那么清晰。事实上,理想的解决方案是根本不使用同义词成员(因为我在SQLAlchemy上下文中使用生成的Enum,它查看的是成员名称,而不是它们的值),而只是在构造期间用"PASSED"悄悄地替换"SUCCESS",但在调用super()的枚举上定义一个自定义的__init__似乎不起作用

Edit:This question and answer提供了迄今为止最简单的解决方案:使用aenum.MultiValueEnum

另外,这里有一个自主开发的解决方案,它似乎符合您在Python 3.6+中应该做的事情的精神,某种程度上受到了@Green-clope-Guy答案的启发:

class EnumSynonymMixin:
    """
    Enum mixin which provides the ability to define synonyms,
    ie. values which can be passed into an enum's constructor, that
    name the same member as one of the defined values, without adding
    any extra members (useful for using with SQLAlchemy's Enum mapping)

    For example:

    class MyEnum(EnumSynonymMixin, enum.Enum):
        FOO = "FOO"
        BAR = "BAR"

        @classmethod
        def synonyms(cls):
            return {"MYFOO": "FOO"}
    
    >>> MyEnum("MYFOO")
    <MyEnum.FOO: 'FOO'>
    """
    @classmethod
    def synonyms(cls):
        """Override to provide a dictionary of synonyms for values that can be
        passed to the constructor"""
        return {}

    @classmethod
    def _missing_(cls, val):
        synonyms = cls.synonyms()
        if val in synonyms:
            return cls.__members__[synonyms[val]]
        return super()._missing(val)


class TrainOutcome(EnumSynonymMixin, enum.Enum):
    PASSED = "PASSED"
    FAILED = "FAILED"
    STARTED = "STARTED"

    @classmethod
    def synonyms(cls):
        return {"SUCCESS": "PASSED"}

Tags: theinreturnfoolineenumfilesynonyms
1条回答
网友
1楼 · 发布于 2024-09-30 16:29:40

这应该是你想要的。本质上,类包装器使TrainOutcome(value)的行为类似于TrainOutcome[value],否则前者会产生错误(如您所描述的情况,您试图用“SUCCESS”调用它)。它通过截取对__new__()的调用并替换第一个参数来实现这一点

根据对你问题的评论,你可能不应该这样做——我想不出为什么TrainOutcome['SUCCESS']不能满足你的需要

def callActsLikeGetitem(c):
    oldnew = c.__new__
    def newwrapper(cls, *args, **kwargs):
        try:
            return oldnew(cls, *args, **kwargs)
        except ValueError:
            if len(args) > 0:
                args = (cls[args[0]].name, *args[1:])
            return oldnew(cls, *args, **kwargs)
    c.__new__ = newwrapper
    return c

@callActsLikeGetitem
class TrainOutcome(enum.Enum):
    PASSED = "PASSED"
    SUCCESS = "PASSED" # Deprecated synonym for "PASSED"
    FAILED = "FAILED"
    STARTED = "STARTED"

TrainOutcome("SUCCESS")
# <TrainOutcome.PASSED: 'PASSED'>

相关问题 更多 >