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

问题描述 投票:0回答:1

我正在标准化现有的、混乱的数据,并且我想创建一个

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'>

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

编辑:现有的具有重复值的Python枚举不能回答我的问题,因为本质上它试图实现与我所追求的相反的目标。那里的OP想让结果值更加明显,我想让它们不那么明显。事实上,理想的解决方案是根本不使用同义词成员(因为我在 SQLAlchemy 上下文中使用生成的

Enum
,它查看成员名称,而不是它们的值),而只是默默地替换
"SUCCESS"
在构造期间使用
"PASSED"
,但在调用
__init__
的枚举上定义自定义
super()
似乎不起作用。

编辑:这个问题和答案提供了迄今为止最简单的解决方案:使用

aenum.MultiValueEnum

否则,这里有一个自行开发的解决方案,它似乎符合您在 Python 3.6+ 中应该如何做的精神,有点受到 @Green Cloak 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"}
python enums
1个回答
0
投票

这应该可以达到你想要的效果。本质上,类包装器使

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'>
© www.soinside.com 2019 - 2024. All rights reserved.