Nathan Kovner
Nathan Kovner

Reputation: 2633

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

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

Upvotes: 174

Views: 188056

Answers (17)

hiro protagonist
hiro protagonist

Reputation: 46879

UPDATE

my answer is outdated. for newer python versions see Filip Poplewski's answer.


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

Upvotes: 244

Filip Poplewski
Filip Poplewski

Reputation: 198

In newer versions of python it is simple as that:

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

print('6 is a Fruit number' if 6 in Fruit else '6 is not a Fruit number')
print('7 is a Fruit number' if 7 in Fruit else '7 is not a Fruit number')

or with usage of the functional syntax

Fruit = Enum('Fruit', [('Apple', 4), ('Orange', 5), ('Pear', 6)])
print('6 is a Fruit number' if 6 in Fruit else '6 is not a Fruit number')
print('7 is a Fruit number' if 7 in Fruit else '7 is not a Fruit number')

output:

6 is a Fruit number
7 is not a Fruit number

Upvotes: 8

Sergey Podushkin
Sergey Podushkin

Reputation: 507

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):
    ...

Upvotes: 0

SuaveSouris
SuaveSouris

Reputation: 1342

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

Upvotes: 0

Berislav Lopac
Berislav Lopac

Reputation: 17243

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"

Upvotes: 47

Konchog
Konchog

Reputation: 2188

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

Upvotes: 6

Val K
Val K

Reputation: 405

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)

Upvotes: -1

Ray Salemi
Ray Salemi

Reputation: 5913

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

Upvotes: 17

Ghasem
Ghasem

Reputation: 15573

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()]

Upvotes: 7

Zia
Zia

Reputation: 2710

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]

Upvotes: 3

Finesse
Finesse

Reputation: 10801

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

Upvotes: 20

ComeOnGetMe
ComeOnGetMe

Reputation: 1117

An EAFP version of the answer:

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

Upvotes: 8

maciek
maciek

Reputation: 3364

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

Upvotes: 2

Joshua Ryan
Joshua Ryan

Reputation: 648

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) 

Upvotes: 4

manu3d
manu3d

Reputation: 1081

Building on what Reda Maachi started:

6 in Fruit.__members__.values() 

returns True

7 in Fruit.__members__.values()  

returns False

Upvotes: 14

JianWei
JianWei

Reputation: 131

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

Upvotes: 5

Reda Maachi
Reda Maachi

Reputation: 857

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

Upvotes: 25

Related Questions