【问题标题】:How to compare Enums in Python?如何比较 Python 中的枚举?
【发布时间】:2017-01-09 03:11:13
【问题描述】:

从 Python 3.4 开始,Enum 类就存在了。

我正在编写一个程序,其中一些常量具有特定的顺序,我想知道哪种方式最适合比较它们:

class Information(Enum):
    ValueOnly = 0
    FirstDerivative = 1
    SecondDerivative = 2

现在有一种方法,需要将给定的informationInformation 与不同的枚举进行比较:

information = Information.FirstDerivative
print(value)
if information >= Information.FirstDerivative:
    print(jacobian)
if information >= Information.SecondDerivative:
    print(hessian)

直接比较不适用于枚举,所以有三种方法,我想知道哪种方法更受欢迎:

方法一:使用价值观:

if information.value >= Information.FirstDerivative.value:
     ...

方法 2:使用 IntEnum:

class Information(IntEnum):
    ...

方法 3:根本不使用枚举:

class Information:
    ValueOnly = 0
    FirstDerivative = 1
    SecondDerivative = 2

每种方法都有效,方法 1 有点冗长,而方法 2 使用不推荐的 IntEnum 类,而方法 3 似乎是在添加 Enum 之前这样做的方式。

我倾向于使用方法 1,但我不确定。

感谢您的建议!

【问题讨论】:

  • 您能引用“不推荐 IntEnum-class”吗? 3.7.1 的文档根本没有弃用它。
  • 当然,来自文档:“对于大多数新代码,强烈建议使用 Enum 和 Flag,因为 IntEnum 和 IntFlag 破坏了枚举的一些语义承诺(通过与整数相比较,因此通过传递到其他不相关的枚举)。 IntEnum 和 IntFlag 应仅在 Enum 和 Flag 不起作用的情况下使用;例如,当整数常量被替换为枚举时,或者为了与其他系统的互操作性。”
  • 方法 1 对我有用,谢谢!

标签: python python-3.x enums compare


【解决方案1】:

如果您想将富比较运算符与Enum 一起使用,您应该始终实现它们。使用functools.total_ordering 类装饰器,您只需要实现一个__eq__ 方法以及一个单一的排序,例如__lt__。由于enum.Enum 已经实现了__eq__,这变得更加容易:

>>> import enum
>>> from functools import total_ordering
>>> @total_ordering
... class Grade(enum.Enum):
...   A = 5
...   B = 4
...   C = 3
...   D = 2
...   F = 1
...   def __lt__(self, other):
...     if self.__class__ is other.__class__:
...       return self.value < other.value
...     return NotImplemented
... 
>>> Grade.A >= Grade.B
True
>>> Grade.A >= 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: Grade() >= int()

IntEnum 可能会发生可怕、可怕、可怕的事情。它主要是为了向后兼容而包含在内的,枚举过去是通过子类化int 来实现的。来自docs

对于绝大多数代码,强烈建议使用 Enum,因为 IntEnum 打破了枚举的一些语义承诺(通过 与整数相当,因此通过传递到其他不相关的 枚举)。它应该只在特殊情况下使用 别无选择;例如,当整数常量被替换为 代码需要枚举和向后兼容性 仍然需要整数。

以下是您不想这样做的示例:

>>> class GradeNum(enum.IntEnum):
...   A = 5
...   B = 4
...   C = 3
...   D = 2
...   F = 1
... 
>>> class Suit(enum.IntEnum):
...   spade = 4
...   heart = 3
...   diamond = 2
...   club = 1
... 
>>> GradeNum.A >= GradeNum.B
True
>>> GradeNum.A >= 3
True
>>> GradeNum.B == Suit.spade
True
>>> 

【讨论】:

  • 很好的描述,非常感谢。只有一个问题:你return NotImplemented 而不是raise NotImplemented。是否有一般规则,何时使用 return 以及何时使用 raise?
  • @SebastianWerk 好吧,你不能raise NotImplemented,因为它不是一个例外。它是一个内置的单例。请参阅docs,它用于富比较运算符的特殊情况。根据docsNotImplementedError 用于“抽象方法在需要派生类覆盖该方法时应该引发此异常。”。
  • @SebastianWerk 另外,请参阅此问题:stackoverflow.com/questions/878943/…
  • 很好的答案,好先生。这种方法是official Python documentation 中详述的OrderedEnum 类的一种简洁(尽管效率较低)的替代方法。虽然手动实现所有比较运算符的OrderedEnum 解决方案 稍快,但上面给出的@total_ordering 解决方案有其优点。简洁是一种吃力不讨好的美德。与此相关,有谁知道为什么 OrderedEnum 只是被记录在案而不是添加到 enum 模块中?
  • 是否有一个 python Enum 构造或类似的实际上有点.. 工作?添加@total_ordering 和实现比较运算符是不是我们应该自己编写代码的东西。这是太多的样板。
【解决方案2】:

我之前没有遇到过 Enum,所以我扫描了文档 (https://docs.python.org/3/library/enum.html) ... 并找到了 OrderedEnum(第 8.13.13.2 节)这不是你想要的吗?来自文档:

>>> class Grade(OrderedEnum):
...     A = 5
...     B = 4
...     C = 3
...     D = 2
...     F = 1
...
>>> Grade.C < Grade.A
True

【讨论】:

  • 由于未修复的 Python 错误,此方法不起作用:bugs.python.org/issue30545 您应该始终覆盖比较器方法,如 eqlt
  • @asu 没有未修复的错误,该线程中的问题不是Enum 本身的问题,并且无论如何,此示例确实覆盖了比较运算符.
  • 似乎无法在 Python 3.6 中导入,ImportError: cannot import name 'OrderedEnum'编辑: 看起来这是一个“有趣的示例”,实际上不在标准 python 库中。您需要从文档中复制 sn-p 才能使用它。
【解决方案3】:

结合上述一些想法,您可以将 enum.Enum 子类化以使其与字符串/数字相当,然后在此类上构建您的枚举:

import numbers
import enum


class EnumComparable(enum.Enum):
    def __gt__(self, other):
        try:
            return self.value > other.value
        except:
            pass
        try:
            if isinstance(other, numbers.Real):
                return self.value > other
        except:
            pass
        return NotImplemented

    def __lt__(self, other):
        try:
            return self.value < other.value
        except:
            pass
        try:
            if isinstance(other, numbers.Real):
                return self.value < other
        except:
            pass
        return NotImplemented

    def __ge__(self, other):
        try:
            return self.value >= other.value
        except:
            pass
        try:
            if isinstance(other, numbers.Real):
                return self.value >= other
            if isinstance(other, str):
                return self.name == other
        except:
            pass
        return NotImplemented

    def __le__(self, other):
        try:
            return self.value <= other.value
        except:
            pass
        try:
            if isinstance(other, numbers.Real):
                return self.value <= other
            if isinstance(other, str):
                return self.name == other
        except:
            pass
        return NotImplemented

    def __eq__(self, other):
        if self.__class__ is other.__class__:
            return self == other
        try:
            return self.value == other.value
        except:
            pass
        try:
            if isinstance(other, numbers.Real):
                return self.value == other
            if isinstance(other, str):
                return self.name == other
        except:
            pass
        return NotImplemented

【讨论】:

    【解决方案4】:

    你也可以创建一个简单的装饰器来解决这个问题:

    from enum import Enum
    from functools import total_ordering
    
    def enum_ordering(cls):
        def __lt__(self, other):
            if type(other) == type(self):
                return self.value < other.value
    
            raise ValueError("Cannot compare different Enums")
    
        setattr(cls, '__lt__', __lt__)
        return total_ordering(cls)
    
    
    @enum_ordering
    class Foos(Enum):
        a = 1
        b = 3
        c = 2
    
    assert Names.a < Names.c
    assert Names.c < Names.b
    assert Names.a != Foos.a
    assert Names.a < Foos.c # Will raise a ValueError
    
    

    对于奖励积分,您可以在上面@VoteCoffee 的回答中实施其他方法

    【讨论】:

      猜你喜欢
      • 2017-02-08
      • 1970-01-01
      • 2014-03-15
      • 1970-01-01
      • 2012-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-14
      相关资源
      最近更新 更多