7

I have created the following Enum:

from enum import Enum

class Action(str, Enum):
    NEW_CUSTOMER = "new_customer"
    LOGIN = "login"
    BLOCK = "block"

I have inherited from str, too, so that I can do things such as:

action = "new_customer"
...
if action == Action.NEW_CUSTOMER:
    ...

I would now like to be able to check if a string is in this Enum, such as:

if "new_customer" in Action:
    ....

I have tried adding the following method to the class:

def __contains__(self, item):
    return item in [i for i in self]

However, when I run this code:

print("new_customer" in [i for i in Action])
print("new_customer" in Action)

I get this exception:

True
Traceback (most recent call last):
  File "/Users/kevinobrien/Documents/Projects/crazywall/utils.py", line 24, in <module>
    print("new_customer" in Action)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/enum.py", line 310, in __contains__
    raise TypeError(
TypeError: unsupported operand type(s) for 'in': 'str' and 'EnumMeta'
KOB
  • 3,458
  • 4
  • 34
  • 72
  • No, I have shown that I can do something like `"new_customer" in [i for i in Action]`, but I want something cleaner such as `"new_customer" in Action`. – KOB Aug 10 '20 at 07:44
  • 1
    I've reopened this question. It's about strings, and doesn't restrict try/catch – Mad Physicist Aug 10 '20 at 13:36

5 Answers5

11

I just bumped into this problem today; I had to change a number of subpackages for Python 3.8.

Perhaps an alternative to the other solutions here is the following, inspired by the excellent answer here to a similar question, as well as @MadPhysicist's answer on this page:

from enum import Enum, EnumMeta


class MetaEnum(EnumMeta):
    def __contains__(cls, item):
        try:
            cls(item)
        except ValueError:
            return False
        return True    


class BaseEnum(Enum, metaclass=MetaEnum):
    pass


class Stuff(BaseEnum):
    foo = 1
    bar = 5

Tests (either in py37 or 38):

>>> 1 in Stuff
True

>>> Stuff.foo in Stuff
True

>>> 2 in Stuff
False

>>> 2.3 in Stuff
False

>>> 'zero' in Stuff
False
Pierre D
  • 19,195
  • 6
  • 50
  • 84
7

You can check if the enum contains a value by calling it:

>>> Action('new_customer')
Action.NEW_CUSTOMER

If the object you pass in is not guarantee to be in the enum, you can use a try block to capture the resulting ValueError. E.g.,

def is_action(obj):
    try:
        Action(obj)
    except ValueError:
        return False
    return True
Mad Physicist
  • 95,415
  • 23
  • 151
  • 231
6

Given an enum of languages

class Language(enum.Enum):
    en = 'en'
    zh = 'zh'

    @classmethod
    def has_member_key(cls, key):
        return key in cls.__members__

print(Language.has_member_key('tu')) => False
print(Language.has_member_key('en')) => True
Shady Smaoui
  • 414
  • 4
  • 5
2

Since Action is a derived class of Enum, we can use the fact that Enum has a member called _value2member_map_.

value2member_map is a private attribute (i.e. Internally in CPython) tthat maps values to names(will only work for hashable values though). However, it's not a good idea to rely on private attributes as they can be changed anytime.

Reference

We get the following:

if "new_customer" in Action._value2member_map_:  # works

which is close to your desired:

if "new_customer" in Action:  # doesn't work (i.e. TypeError)
DarrylG
  • 14,084
  • 2
  • 15
  • 21
0

You can also check contains in enum by brackets like in dict

class Action(Enum):
    NEW_CUSTOMER = 1
    LOGIN = 2
    BLOCK = 3

action = 'new_customer'
try:
    action = Action[action.upper()]
    print("action type exists")
except KeyError:
    print("action type doesn't exists")
Pavivin
  • 11
  • 2