Overriding __contains__ method for a class

metaclass, python, python-3.x

Solution

Why that?

When you use special syntax like `a in Foo`, the `__contains__` method is looked up on the type of `Foo`. However, your `__contains__` implementation exists on `Foo` itself, not its type. `Foo`'s type is `type`, which doesn't implement this (or iteration), thus the error.

The same situation occurs if you instantiate an object and then, after it is created, add a `__contains__` function to the instance variables. That function won't be called:

>>> class Empty: pass
... 
>>> x = Empty()
>>> x.__contains__ = lambda: True
>>> 1 in x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: argument of type 'Empty' is not iterable

Can I somehow get the syntactic sugar I want?

Yes. As mentioned above, the method is looked up on `Foo`'s type. The type of a class is called a metaclass, so you need a new metaclass that implements `__contains__`.

Try this one:

class MetaEnum(type):
    def __contains__(cls, x):
            return x in range(cls.k)

As you can see, the methods on a metaclass take the metaclass instance -- the class -- as their first argument. This should make sense. It's very similar to a classmethod, except that the method lives on the metaclass and not the class.

Inheritance from a class with a custom metaclass also inherits the metaclass, so you can create a base class like so:

class BaseEnum(metaclass=MetaEnum):
    pass

class MyEnum(BaseEnum):
    k = 3

print(1 in MyEnum) # True

Problem

I need to simulate enums in Python, and did it by writing classes like: ``` class Spam(Enum): k = 3 EGGS = 0 HAM = 1 BAKEDBEANS = 2 ``` Now I'd like to test if some constant is a valid choice for a particular Enum-derived class, with the following syntax: ``` if (x in Foo): print("seems legit") ``` Therefore I tried to create an "Enum" base class where I override the `__contains__` method like this: ``` class Enum: """ Simulates an enum. """ k = 0 # overwrite in subclass with number of constants @classmethod def __contains__(cls, x): """ Test for valid enum constant x: x in Enum """ return (x in range(cls.k)) ``` However, when using the `in` keyword on the class (like the example above), I get the error: ``` TypeError: argument of type 'type' is not iterable ``` Why that? Can I somehow get the syntactic sugar I want?

Original source

Related problems