【问题标题】:How do I test if int value exists in Python Enum without using try/catch?如何在不使用 try/catch 的情况下测试 Python Enum 中是否存在 int 值?
【发布时间】:2017-09-23 21:09:20
【问题描述】:

使用 Python Enum 类,有没有办法在不使用 try/catch 的情况下测试 Enum 是否包含特定的 int 值?

使用以下类:

from enum import Enum

class Fruit(Enum):
    Apple = 4
    Orange = 5
    Pear = 6

如何测试值 6(返回 true)或值 7(返回 false)?

【问题讨论】:

  • 在 Python 中使用 try/except 绝对没问题(尽管我承认我很难得到它)。那么,你不使用它的原因是什么?

标签: python enums


【解决方案1】:
class MyEnumMixin:

    raw_values = None  # for IDE autocomplete

    def __new__(cls, value):
        if 'raw_values' not in cls.__dict__:
            cls.raw_values = set()
        cls.raw_values.add(value)
        if cls.__bases__[0] is MyEnumMixin:
            member = object().__new__(cls)
        else:
            member = super().__new__(cls, value)
        member._value_ = value
        return member



class MyEnum(MyEnumMixin, Enum):
    FOO = 1
    BAR = 'bar'

print(1 in MyEnum.raw_values)
print('bar' in MyEnum.raw_values)



class MyStrEnumMixin(MyEnumMixin, str):
    pass

class MyStrEnum(MyStrEnumMixin, Enum):
    FOO = 'foo'
    BAR = 'bar'

print('foo' in MyStrEnum.raw_values)
print('bar' in MyStrEnum.raw_values)

【讨论】:

    【解决方案2】:

    有一种方法可以让所有枚举都能够检查项目是否存在:

    import enum 
    
    class MyEnumMeta(enum.EnumMeta): 
        def __contains__(cls, item): 
            return item in [v.value for v in cls.__members__.values()] 
    
    class MyEnum(enum.Enum, metaclass=MyEnumMeta): 
       FOO = "foo" 
       BAR = "bar"
    

    现在您可以进行简单的检查了:

    >>> "foo" in MyEnum
    True
    

    如果所有枚举的值总是相同的类型,甚至可以变得更简单——例如字符串:

    import enum 
     
    class MyEnumMeta(enum.EnumMeta):  
        def __contains__(cls, item): 
            return item in cls.__members__.values()
    
    class MyEnum(str, enum.Enum, metaclass=MyEnumMeta): 
        FOO = "foo" 
        BAR = "bar"
    

    编辑:另一个版本,技术上最正确的版本:

    import enum 
    
    class MyEnumMeta(enum.EnumMeta): 
        def __contains__(cls, item): 
            try:
                cls(item)
            except ValueError:
                return False
            else:
                return True
    
    class MyEnum(enum.Enum, metaclass=MyEnumMeta): 
       FOO = "foo" 
       BAR = "bar"
    

    【讨论】:

      【解决方案3】:

      我只是将 IntEnum 转换为列表并正常测试:

      from enum import IntEnum
      class Foo(IntEnum):
          ONE = 1
          TWO = 2
          THREE = 3
      
      print(1 in list(Foo))
      True
      print(4 in list(Foo))
      False
      

      【讨论】:

        【解决方案4】:

        您可以使用__members__ special attribute 来迭代成员:

        from enum import Enum
        
        class Fruit(Enum):
            Apple = 4
            Orange = 5
            Pear = 6
        
            @staticmethod
            def has_value(item):
                return item in [v.value for v in Fruit.__members__.values()]
        

        【讨论】:

          【解决方案5】:

          这个怎么样?

          from enum import Enum
          
          class Fruit(Enum):
              Apple = 4
              Orange = 5
              Pear = 6
          
          has_apples = 4 in [n.value for n in Fruit]
          

          这会让你也这样做:

          has_apples = "Apple" in [n.name for n in Fruit]
          

          【讨论】:

            【解决方案6】:

            如果枚举有很多成员,这种方法会更快,因为它不会创建新列表并在找到给定值时停止遍历枚举:

            any(x.value == 5 for x in Fruit)  # True
            any(x.value == 7 for x in Fruit)  # False
            

            【讨论】:

            • 这个应该更高...简单有效,谢谢!
            【解决方案7】:

            EAFP 版本的答案:

            try: 
                Fruit(val)
                return True
            except ValueError:
                return False
            

            【讨论】:

              【解决方案8】:

              IntEnum + __members__

              您可以使用IntEnum__members__ 来实现所需的行为:

              from enum import IntEnum
              
              class Fruit(IntEnum):
                  Apple = 4
                  Orange = 5
                  Pear = 6
              
              >>> 6 in Fruit.__members__.values()
              True
              >>> 7 in Fruit.__members__.values()
              False
              

              Enum + 列表理解 + .value

              如果你必须/想要坚持Enum,你可以这样做:

              >>> 6 in [f.value for f in Fruit]
              True
              >>> 7 in [f.value for f in Fruit]
              False
              

              EAPF + ValueError

              或者您可以使用比许可更容易请求宽恕的方法

              try:
                  Fruit(x)
              except ValueError:
                  return False
              else:
                  return True
              

              【讨论】:

                【解决方案9】:

                还有另一种没有人提到的班轮解决方案:

                is_value_in_fruit = any(f.value == value_to_check for f in Fruit)
                

                另外,如果你使用IntEnum 而不是Enum,(class Fruit(IntEnum))你可以这样做

                is_value_in_fruit = any(f == value_to_check for f in Fruit) 
                

                【讨论】:

                  【解决方案10】:

                  测试值

                  变体 1

                  请注意,Enum 有一个名为 _value2member_map_ 的成员(未记录,可能会在未来的 python 版本中更改/删除):

                  print(Fruit._value2member_map_)
                  # {4: <Fruit.Apple: 4>, 5: <Fruit.Orange: 5>, 6: <Fruit.Pear: 6>}
                  

                  您可以针对此地图测试您的Enum 中是否有值:

                  5 in Fruit._value2member_map_  # True
                  7 in Fruit._value2member_map_  # False
                  

                  变体 2

                  如果您不想依赖此功能,这是一种替代方法:

                  values = [item.value for item in Fruit]  # [4, 5, 6]
                  

                  或者(可能更好):使用setin 操作符会更高效:

                  values = set(item.value for item in Fruit)  # {4, 5, 6}
                  

                  然后测试

                  5 in values  # True
                  7 in values  # False
                  

                  has_value 添加到您的班级

                  然后您可以将其作为方法添加到您的类中:

                  class Fruit(Enum):
                      Apple = 4
                      Orange = 5
                      Pear = 6
                  
                      @classmethod
                      def has_value(cls, value):
                          return value in cls._value2member_map_ 
                  
                  print(Fruit.has_value(5))  # True
                  print(Fruit.has_value(7))  # False
                  

                  按键测试

                  如果您想测试名称(而不是值),我会使用 _member_names_

                  'Apple' in Fruit._member_names_  # True
                  'Mango' in Fruit._member_names_  # False
                  

                  【讨论】:

                  • 我个人更喜欢第二种方案。谢谢。
                  • 这两个建议都不是特别节省内存或时间,尽管在这些方面也不是很重。就我而言,我正在使用标准库中的 HTTPStatus 枚举,该枚举目前包含 57 个条目。我永远不会处理来自枚举的整个值列表,并且缓存它们是我想避免的事情。
                  • @none 如果您担心速度,您可以创建一个set 用于查找。这将使用一些额外的内存,但不会很大......
                  • 为什么要在返回值两边加上括号?
                  • 必须在 Python3 中使用return any(value == item.value[0] for item in cls) 枚举结构是否发生了变化?
                  【解决方案11】:

                  不要。

                  如果你使用的是枚举,你可以用

                  测试枚举
                       if isinstance(key, Fruit):
                  

                  但除此之外,try.. 是测试枚举的 Python 方式。确实,对于鸭式打字范式的任何突破。

                  在 IntEnum 中测试 int 的正确且 Python 式的方法是试一试,如果失败则捕获 ValueError。

                  上面提出的许多解决方案都已被积极弃用,并将在 3.8 中被禁止(“DeprecationWarning: using non-Enums in containment checks will raise TypeError in Python 3.8”)

                  如果您真的对保持代码现代化不感兴趣,那么您可以使用

                  if key in Fruit:
                  

                  【讨论】:

                  • 这个解决方案其实是错误的。由于 OP 询问如何检查一个值是否在枚举中,例如 6 或 7。这将始终返回 false,因为枚举的成员实际上都不等于 6 或 7。试试吧。
                  【解决方案12】:

                  在 Reda Maachi 开始的基础上构建:

                  6 in Fruit.__members__.values() 
                  

                  返回真

                  7 in Fruit.__members__.values()  
                  

                  返回假

                  【讨论】:

                  • 这看起来很干净。不幸的是,当我对此进行测试时(至少在 Py3.7 中),它并没有那样工作。 Fruit.__members__.values() 的计算结果为 odict_values([, , ]),上面对 6 和 7 的测试都返回 False我。但这返回 true:&gt;&gt;&gt; Fruit.Orange in Fruit.__members__.values() True
                  • 我想指出,这仅适用于 IntEnum。
                  【解决方案13】:

                  只需检查它是否在Enum. _value2member_map_

                  In[15]: Fruit._value2member_map_
                  Out[15]: {4: <Fruit.Apple: 4>, 5: <Fruit.Orange: 5>, 6: <Fruit.Pear: 6>}
                  
                  In[16]: 6 in Fruit._value2member_map_
                  Out[16]: True
                  
                  In[17]: 7 in Fruit._value2member_map_
                  Out[17]: False
                  

                  【讨论】:

                  • _value2member_map_ 是一个未记录的功能,因此最好避免使用它。
                  【解决方案14】:

                  你可以使用Enum.__members__ - an ordered dictionary mapping names to members:

                  In [12]: 'Apple' in Fruit.__members__
                  Out[12]: True
                  
                  In [13]: 'Grape' in Fruit.__members__
                  Out[13]: False
                  

                  【讨论】:

                  • 问题是要求测试一个 int 值,而不是一个字符串。
                  • 枚举中没有包含这些值的成员吗?这不奇怪吗?
                  • 这与问题所问的方向相反。
                  • 这不适用于值!(它用于“键”)回答者可能有Apple = "Apple",所以他有相同的字母和字母大小写用于键和value ...如果你有APPLE = "Apple",那么.__members__ 将导致{'APPLE': &lt;Fruit.APPLE: 'Apple'&gt;, 'GRAPE': &lt;Fruit.GRAPE: 'Grape'&gt;}
                  猜你喜欢
                  • 1970-01-01
                  • 2012-05-05
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-07-14
                  • 1970-01-01
                  • 2019-12-23
                  相关资源
                  最近更新 更多