163

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)?

Ivan
  • 5,803
  • 2
  • 29
  • 46
Nathan Kovner
  • 2,513
  • 3
  • 18
  • 14
  • 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? – mikuszefski Oct 12 '21 at 13:23

15 Answers15

223

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
hiro protagonist
  • 44,693
  • 14
  • 86
  • 111
  • 13
    I personally prefer the second solution. Thank you. – Aliakbar Abbasi Sep 03 '17 at 05:05
  • 3
    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. – Acsor Sep 18 '17 at 16:51
  • 2
    @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... – hiro protagonist Sep 18 '17 at 18:05
  • Why the parentheses around the return value? – Konstantin Jan 02 '18 at 11:16
  • Had to use `return any(value == item.value[0] for item in cls)` with Python3 did the Enum structure change? – lony Dec 20 '18 at 15:00
  • Beware: using non-Enums in containment checks will raise TypeError in Python 3.8 – Pedro Lourenço Jul 01 '20 at 11:44
  • 1
    @PedroLourenço: circumventing this is the whole point of the OP. This answer (and many others) work fine in 3.8 even for non-Enums – MestreLion Jan 21 '23 at 11:00
42

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"
Berislav Lopac
  • 16,656
  • 6
  • 71
  • 80
  • 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) – Wör Du Schnaffzig Jul 06 '22 at 14:18
  • 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. – Berislav Lopac Jul 06 '22 at 16:20
  • 1
    @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. – F. Sonntag Jul 29 '22 at 04:45
  • 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) – MestreLion Jan 21 '23 at 10:19
  • 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. – MestreLion Jan 21 '23 at 11:30
  • @MestreLion - 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 https://docs.python.org/3/library/enum.html#notes for an example. – Berislav Lopac Jan 22 '23 at 11:27
  • @MestreLion - 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. – Berislav Lopac Jan 22 '23 at 11:30
  • 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 be`True` just like `"foo" in MyEnum` is. – MestreLion Jan 22 '23 at 14:03
  • @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_. – MestreLion Jan 22 '23 at 14:10
  • @MestreLion - 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. – Berislav Lopac Jan 23 '23 at 15:35
  • 1
    @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. – MestreLion Jan 23 '23 at 15:49
25

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
Reda Maachi
  • 853
  • 6
  • 15
  • 9
    The question is asking to test for an int value, not a string. – Nathan Kovner Apr 26 '17 at 12:55
  • 9
    Is there no member in Enum that contains the values? Isn't that strange? – Konstantin Jan 02 '18 at 11:14
  • 5
    This goes in the opposite direction from what the question asked. – user2357112 Oct 01 '18 at 00:59
  • **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': , 'GRAPE': }`** – jave.web May 13 '21 at 10:17
15

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
Finesse
  • 9,793
  • 7
  • 62
  • 92
11

Building on what Reda Maachi started:

6 in Fruit.__members__.values() 

returns True

7 in Fruit.__members__.values()  

returns False

manu3d
  • 991
  • 1
  • 8
  • 23
  • 4
    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([, , ]), and both the tests above for 6 and 7 return False for me. But this returns true: ```>>> Fruit.Orange in Fruit.__members__.values() True``` – nrshapiro Oct 06 '19 at 17:40
  • 6
    I'd like to point out that this works as written for IntEnum only. – Yandros Dec 27 '19 at 01:11
11

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
Ray Salemi
  • 5,247
  • 4
  • 30
  • 63
  • That should be the answer! – Давид Шико Jun 03 '22 at 07:39
  • 4
    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()]` – ryanjdillon Oct 05 '22 at 11:50
  • @ryanjdillon 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. [doc](https://docs.python.org/3/library/enum.html#enum.EnumType.__iter__) – function2 Apr 21 '23 at 09:36
7

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
Konchog
  • 1,920
  • 19
  • 23
  • 4
    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. – Joshua Ryan Jan 24 '20 at 21:42
  • 2
    But this does do what most people of googling for, how to check if a value is an enum, upvoted! Thank you! – run_the_race May 30 '22 at 17:57
  • 2
    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". – eXhumer Nov 29 '22 at 01:43
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()]
Ignacio Vergara Kausel
  • 5,521
  • 4
  • 31
  • 41
Ghasem
  • 14,455
  • 21
  • 138
  • 171
5

An EAFP version of the answer:

try: 
    Fruit(val)
    return True
except ValueError:
    return False
ComeOnGetMe
  • 1,029
  • 9
  • 11
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
JianWei
  • 121
  • 2
  • 6
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) 
Joshua Ryan
  • 628
  • 5
  • 12
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]
Zia
  • 2,735
  • 3
  • 30
  • 27
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
maciek
  • 3,198
  • 2
  • 26
  • 33
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
SuaveSouris
  • 1,302
  • 17
  • 20
-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)

Val K
  • 375
  • 2
  • 9