How do I test if int value exists in Python Enum without using try/catch?
Asked Answered
M

16

173

Using the Python Enum class, is there a way to test if an Enum contains a specific int value without using try/catch?

With the following class:

from enum import Enum

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

How can I test for the value 6 (returning true), or the value 7 (returning false)?

Missy answered 26/4, 2017 at 12:45 Comment(1)
Using try/except in Python is absolutely okay (although I admit that I had a hard time to get it). So, what is the reason for you not to use it?Eclipse
U
239

test for values

variant 1

note that an Enum has a member called _value2member_map_ (which is undocumented and may be changed/removed in future python versions):

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

you can test if a value is in your Enum against this map:

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

variant 2

if you do not want to rely on this feature this is an alternative:

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

or (probably better): use a set; the in operator will be more efficient:

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

then test with

5 in values  # True
7 in values  # False

add has_value to your class

you could then add this as a method to your class:

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

starting form python 3.9 (?) python offers IntEnum. with these you could do this:

from enum import IntEnum

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

print(6 in iter(Fruit))  # True

note there is no need to create a list; just iterating over iter(Fruit) will do. again, if this is needed repeatedly it may be worth creating a set as above:

values = set(Fruit)
print(5 in values)  # True

test for keys

if you want to test for the names (and not the values) i would use _member_names_:

'Apple' in Fruit._member_names_  # True
'Mango' in Fruit._member_names_  # False
Uvulitis answered 26/4, 2017 at 12:49 Comment(7)
I personally prefer the second solution. Thank you.Audi
Either advice is not particularly memory- or time-efficient, although also not very heavy in these terms. In my case, I am working with the HTTPStatus enum from the standard library which as of now contains 57 entries. I would never process a whole list of values from an enumeration, and caching them is something I'd like to avoid.Silage
@none if you are worried about speed you could create a set for the lookup. that would use some additional memory but not a significant amount...Uvulitis
Why the parentheses around the return value?Hiedihiemal
Had to use return any(value == item.value[0] for item in cls) with Python3 did the Enum structure change?Chilly
Beware: using non-Enums in containment checks will raise TypeError in Python 3.8Bagatelle
@PedroLourenço: circumventing this is the whole point of the OP. This answer (and many others) work fine in 3.8 even for non-EnumsJarry
T
46

There is a way to have all the enums be able to check if an item is present:

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"

Now you can do an easy check:

>>> "foo" in MyEnum
True

It can even be made simpler if all the enum's values will always be the same type -- for example strings:

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"

Edit: As per the comments by @MestreLion, I've updated the first example to include the isinstance check; without it the test fails if we're checking an already instantiated enum value.

Edit: Yet another version, technically the most correct one:

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"
Teleprinter answered 11/7, 2020 at 20:57 Comment(11)
A works, B works not, C works. Counter Example: class A(enum.Enum): ABC = 123. 123 in A.__members__.values() results in False. (Python 3.6)Dauntless
I'm not sure what you mean by "B works not"; all three examples work fine. Also, I'm not sure what your "counter example" means; this is exactly the expected behaviour.Teleprinter
@berislav-lopac The counter example is meant for the B case. BUT @avans it is wrong, missing the critical constriction that the MyEnum only contains of str values, in case B.Soap
Could you please expand the answer explaining why the B case requires values to be homogeneous? Is it because case B fails if item is an instance of A? So what it actually requires is that A(x) == x ? Wow, that's subtle, and it took a while for me to figure it out (that is, if I got it right)Jarry
By the way, method A also requires this, no? MyEnum("foo") in MyEnum == False> I don't understand what advantage method A has over method B.Jarry
@Jarry - If you look closely, you will see that the definition of MyEnum in case B includes str as one of the classes it's inheriting, which means that all values need to be strings. In 3.11 this was made redundant by introducing the StrEnum class (just like IntEnum which has existed for a long time); but you can still use the same trick with other types - see docs.python.org/3/library/enum.html#notes for an example.Teleprinter
@Jarry - The whole point of this question (and the answers) is about checking whether a value is in an enum's definition without instantiating it. So MyEnum("foo") in MyEnum can by definition never be False - if "foo" is not one of the MyEnum values the MyEnum("foo") will raise an exception. So the check we're talking about is "foo" in MyEnum; the example A is generic and works on any value type, while B is only when the values are of the same type and that type is set as one of the MyEnum's superclasses.Teleprinter
I'm afraid I did not express myself properly: I do understand the difference between methods A and B, I just can't see the advantage of method A, as it seems to have the same requirement of B: both require typed Enums: if using method A the statement a = MyEnum("foo"); a in MyEnum will evaluate to False, when it should beTrue just like "foo" in MyEnum is.Jarry
@BerislavLopac: And the "subtlety" I mentioned about B method is that being a "typed" Enum is not actually a requirement, just a means to an end. The actual requirement is that E(x) == x, which can be achieved with a typed enum. Otherwise it will give undesired results for MyEnum("foo") in MyEnum. If we the goal is to allow membership test by value, I believe it should also work if used with instances.Jarry
@Jarry - Ah I see your point now, and you are correct - it will fail if you are looking at an instance of MyEnum in MyEnum. But that can be easilly resolved by adding isinstance(item, cls) into the check in __contains__. I'll update the answer accordingly.Teleprinter
@BerislavLopac: method B does not require the additional isinstance() check: as its members are also instances of str, instances of MyEnum will pass the item in cls.__members__.values() check. And you can use your original simplification item in cls, it will work for typed Enums.Jarry
F
24

You could use 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
Frater answered 26/4, 2017 at 12:47 Comment(4)
The question is asking to test for an int value, not a string.Missy
Is there no member in Enum that contains the values? Isn't that strange?Hiedihiemal
This goes in the opposite direction from what the question asked.Equivoque
This doesn't work for values! (it's for "keys") answerer probably had Apple = "Apple", so he had the same letters & lettercase used for both key and value ... if you'd have APPLE = "Apple" then .__members__ will result in {'APPLE': <Fruit.APPLE: 'Apple'>, 'GRAPE': <Fruit.GRAPE: 'Grape'>}Incompressible
M
19

If the enum has many members, this approach can be faster because it doesn't make a new list and stops walking the enum when the given value is found:

any(x.value == 5 for x in Fruit)  # True
any(x.value == 7 for x in Fruit)  # False
Maryellen answered 2/8, 2020 at 1:34 Comment(1)
this should be much higher... simple and effective, thanks!Khabarovsk
H
15

I just convert an IntEnum to a list and test it normally:

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
Huffish answered 3/1, 2021 at 11:42 Comment(3)
That should be the answer!Paperweight
Perhaps this only works for IntEnum. I needed to do the following to get the member values as a list [m.value for m in MyEnum.__members__.values()]Rubalcava
@Rubalcava No, this works for all enum types. Since EnumMeta (which is renamed to EnumType since Python 3.11) implements __iter__, all enum types are iterable and can be converted to a list. docStupid
P
12

Building on what Reda Maachi started:

6 in Fruit.__members__.values() 

returns True

7 in Fruit.__members__.values()  

returns False

Pes answered 3/5, 2019 at 9:2 Comment(2)
This looked like a very clean way. Unfortunately, when I tested this (at least in Py3.7) it doesn't work like that. Fruit.__members__.values() evaluates to odict_values([<Fruit.Apple: 4>, <Fruit.Orange: 5>, <Fruit.Pear: 6>]), and both the tests above for 6 and 7 return False for me. But this returns true: >>> Fruit.Orange in Fruit.__members__.values() TrueFeatherhead
I'd like to point out that this works as written for IntEnum only.Coact
K
7

An EAFP version of the answer:

try: 
    Fruit(val)
    return True
except ValueError:
    return False
Kym answered 13/10, 2019 at 23:32 Comment(0)
H
6

Originally, Don't.

If you are using Enum, you can test for enum with

     if isinstance(key, Fruit):

But otherwise, try.. is the pythonic way to test for enum. Indeed, for any break in the duck-typing paradigm.

The correct, and pythonic, way of testing for an int in an IntEnum is to give it a go and to catch a ValueError if there's a failure.

Many of the solutions proposed above are actively deprecated and will be disallowed by 3.8 ( "DeprecationWarning: using non-Enums in containment checks will raise TypeError in Python 3.8" )

From Python 3.8 – Coerce it into a list.

After 3.8 or so, you can test by coercion of the enum into a list as follows (using 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)}')

Which will print as follows

0, False
1, False
2, True
3, False
4, True
5, True
6, True
7, False
Heinous answered 23/9, 2019 at 10:56 Comment(3)
This solution is actually wrong. Since OP asked how to check if a value was in an enum, such as 6, or 7. This will always return false, since none of the members of the enum are actually equal to 6 or 7. Try it out.Sawyers
But this does do what most people of googling for, how to check if a value is an enum, upvoted! Thank you!Hatten
The DeprecationWarning mentioned in this answer will be outdated starting from Python 3.12. Python 3.11 now includes information notifying of the upcoming change in 3.12. "DeprecationWarning: in 3.12 contains will no longer raise TypeError, but will return True or False depending on whether the value is a member or the value of a member".Stovall
A
6

You could use __members__ special attribute to iterate over 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()]
Augmentation answered 13/1, 2020 at 8:36 Comment(0)
I
4

Just check whether it's in 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
Izabel answered 19/11, 2018 at 8:41 Comment(1)
_value2member_map_ is an undocumented feature, so it's best to avoid using it.Foundling
S
3

There's another one liner solution nobody has mentioned yet:

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

Also, if you use IntEnum instead of Enum, (class Fruit(IntEnum)) you can just do this

is_value_in_fruit = any(f == value_to_check for f in Fruit) 
Sawyers answered 24/1, 2020 at 21:39 Comment(0)
D
2

how about this?

from enum import Enum

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

has_apples = 4 in [n.value for n in Fruit]

This would let you also do:

has_apples = "Apple" in [n.name for n in Fruit]
Disfrock answered 2/12, 2020 at 20:43 Comment(0)
J
1

IntEnum + __members__

You could use IntEnum and __members__ to achieve required behaviour:

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 + list comprehension + .value

If you must/want stick to Enum, you can do:

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

EAPF + ValueError

Or you can use easier to ask for forgiveness than permission method:

try:
    Fruit(x)
except ValueError:
    return False
else:
    return True
Joleen answered 21/2, 2020 at 20:49 Comment(0)
M
0

I find the cleanest and most familiar way to do this is with 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
Madel answered 26/5, 2023 at 15:37 Comment(0)
M
0

Summarizing all the above and creating a new class that can be used instead of simple 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):
    ...

Having the code in the better_enum.py one can just import the BetterEnum instead of the enum.Enum and use it as usual with extended "in" checks:

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"

With this subclassing, you'll have a simple way to check if your enum has an arbitrary value:

6 in Fruit # returns True
"Banana" in Fruit # returns False
"blue" in Colors # returns True
"Banana" in Colors # returns False

Of course, you can use the BetterEnum instead of the enum.Enum without changing your class definitions with:

from better_enum import BetterEnum as Enum

class Fruit(Enum):
    ...

Metalepsis answered 24/4 at 19:12 Comment(0)
S
-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)

Steatite answered 23/12, 2021 at 8:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.