使用Python Enum类,有没有一种方法可以在不使用try/catch的情况下测试Enum是否包含特定的int值?
有以下课程:
from enum import Enum
class Fruit(Enum):
Apple = 4
Orange = 5
Pear = 6
如何测试值 6(返回 true)或值 7(返回 false)?
注意
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
如果您不想依赖此功能,这是一个替代方案:
values = [item.value for item in Fruit] # [4, 5, 6]
或(可能更好):使用
set
; in
运算符会更高效:
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
IntEnum
。有了这些你就可以做到这一点:
from enum import IntEnum
class Fruit(IntEnum):
Apple = 4
Orange = 5
Pear = 6
print(6 in iter(Fruit)) # True
注意,无需创建
list
;只需迭代 iter(Fruit)
即可。再次强调,如果重复需要,可能值得像上面那样创建一个集合:
values = set(Fruit)
print(5 in values) # True
如果你想测试名称(而不是值),我会使用
_member_names_
:
'Apple' in Fruit._member_names_ # True
'Mango' in Fruit._member_names_ # False
有一种方法可以让所有枚举都能够检查某个项目是否存在:
import enum
class MyEnumMeta(enum.EnumMeta):
def __contains__(cls, item):
return isinstance(item, cls) or 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"
编辑:根据@MestreLion的评论,我更新了第一个示例以包含
isinstance
检查;如果没有它,如果我们检查已经实例化的枚举值,测试就会失败。
编辑:又一个版本,技术上最正确的版本:
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"
您可以使用
Enum.__members__
- 将名称映射到成员的有序字典:
In [12]: 'Apple' in Fruit.__members__
Out[12]: True
In [13]: 'Grape' in Fruit.__members__
Out[13]: False
如果枚举有许多成员,这种方法可能会更快,因为它不会创建新列表,并且在找到给定值时停止遍历枚举:
any(x.value == 5 for x in Fruit) # True
any(x.value == 7 for x in Fruit) # False
我只是将 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
以 Reda Maachi 的起步为基础:
6 in Fruit.__members__.values()
返回真
7 in Fruit.__members__.values()
返回错误
答案的EAFP版本:
try:
Fruit(val)
return True
except ValueError:
return False
本来就不要。
如果您使用 Enum,您可以使用
测试枚举 if isinstance(key, Fruit):
但除此之外, try.. 是测试枚举的 Python 方式。事实上,对于鸭子打字范式的任何突破。
测试 IntEnum 中 int 的正确且 Python 的方法是尝试一下,如果失败则捕获 ValueError。
上面提出的许多解决方案已被积极弃用,并将在 3.8 版本中被禁止(“DeprecationWarning:在包含检查中使用非枚举将在 Python 3.8 中引发 TypeError”)
从 Python 3.8 开始 – 将其强制转换为列表。
在 3.8 左右之后,您可以通过将枚举强制转换为列表来进行测试,如下所示(使用 IntEnum)
from enum import IntEnum
class Fruit(IntEnum):
Kiwi = 2
Apple = 4
Orange = 5
Pear = 6
for x in range(8):
print(f'{x}, {x in list(Fruit)}')
将打印如下
0, False
1, False
2, True
3, False
4, True
5, True
6, True
7, False
__members__
特殊属性 来迭代成员:
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()]
只需检查它是否在
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
还有另一种还没有人提到的衬垫解决方案:
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)
这个怎么样?
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]
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
我发现最干净、最熟悉的方法是使用
try/except
:
class Fruit(Enum):
Apple = 4
Orange = 5
Pear = 6
# If you want it in function form
def is_fruit(x):
try:
Fruit(x)
except ValueError:
return False
else:
return True
总结以上所有内容并创建一个可以用来代替简单 Enum 的新类:
from enum import Enum, EnumMeta
class BetterEnumMeta(EnumMeta):
def __contains__(cls, item):
if isinstance(item, Enum):
return item.__class__ is cls
return any(item == member.value for member in cls.__members__.values())
class BetterEnum(Enum, metaclass=BetterEnumMeta):
...
将代码放在
better_enum.py
中,只需导入 BetterEnum
而不是 enum.Enum
并像往常一样使用扩展的“in”检查:
from better_enum import BetterEnum
class Fruit(int, BetterEnum):
Apple = 4
Orange = 5
Pear = 6
class Colors(str, BetterEnum):
red = "red"
blue = "blue"
black = "black"
通过这个子类化,您将有简单的方法来检查您的枚举是否具有任意值:
6 in Fruit # returns True
"Banana" in Fruit # returns False
"blue" in Colors # returns True
"Banana" in Colors # returns False
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)