__contains__ and Python3.8 enum.Enum

Question:

SETUP: Python 3.8.2; Some Enum Class with an overloaded __contains__() function.

  from enum import Enum
  
  class Something(Enum):
      A = 1
      def __contains__(self, Other):
          return Other in self.__class__.__members__.keys()

TEST 1: Using values from the enum itself.

  print("A:", Something.A in Something)

works fine (here result = A: True).

TEST 2: Using non-enum values.

  print("1:", 1 in Something)

fails with an exception, namely

  TypeError: unsupported operand type(s) for 'in': 'int' and 'EnumMeta'

QUESTION:

How is it possible to achieve an in functionality (membership operator) where the left operand can be anything? That is, it should be possible to write something like

if anything in Something:
    ...

without having to check the type of anything.

Answers:

Defining Something.__contains__ lets you write something like 1 in Something.A. For what you want, you would need to subclass EnumMeta and use the result in defining Something.

In some sense, Enum.__new__ already does the check we want; passing a value to the type either returns the appropriate instance, or raises a ValueError.

>>> Something.A
<Something.A: 1>
>>> Something(Something.A)
<Something.A: 1>
>>> Something(1)
<Something.A: 1>
>>> Something(3)
ValueError: 3 is not a valid Something

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
[...]

So, our new metaclass’s __contains__ method simply has to try to retrieve the given value from the given type, returning True if it succeeds and False if an exception is raised.

from enum import EnumMeta, Enum

class MyMeta(EnumMeta):

    def __contains__(self, other):
        try:
            self(other)
        except ValueError:
            return False
        else:
            return True


class Something(Enum, metaclass=MyMeta):
    A = 1


assert Something.A in Something
assert 1 in Something
assert 2 not in Something

If you want 1 in Something to return False instead, just catch the TypeError raised by super().__contains__.

class MyMeta(EnumMeta):
    def __contains__(self, other):
        try:
            return super().__contains__(other)
        except TypeError:
            return False
Answered By: chepner
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.