__contains__

__contains__(item)
Returns: bool · Updated March 18, 2026 · Keywords
dunder-methods operators containers magic-methods

The __contains__ method defines behavior for the membership test operators in and not in. When you use these operators, Python calls this method to determine whether an item exists in your object.

Signature

def __contains__(self, item) -> bool:

The method takes a single argument — the item to check for membership — and must return a boolean value (True or False).

How It Works

When Python evaluates item in container, it translates it to:

container.__contains__(item)

If __contains__ is not defined, Python falls back to iterating through the object using __iter__ and checking each item with the equality operator.

Basic Example

Here’s a simple container that delegates to an internal list:

class MyContainer:
    def __init__(self, items):
        self.items = items

    def __contains__(self, item):
        return item in self.items

container = MyContainer([1, 2, 3, 4, 5])
print(3 in container)   # True
print(6 in container)  # False

Custom Membership Logic

You can define membership rules that go beyond simple equality checks:

class EvenNumbers:
    def __contains__(self, num):
        return isinstance(num, int) and num % 2 == 0

evens = EvenNumbers()
print(4 in evens)   # True
print(5 in evens)   # False
print("a" in evens) # False

This container doesn’t store numbers — it defines membership mathematically.

Practical Use Cases

Case-Insensitive String Container

class CaseInsensitiveContainer:
    def __init__(self, items):
        self.items = [item.lower() for item in items]

    def __contains__(self, item):
        return item.lower() in self.items

fruits = CaseInsensitiveContainer(['Apple', 'Banana', 'Orange'])
print('apple' in fruits)   # True
print('BANANA' in fruits)  # True
print('pear' in fruits)     # False

Range-Based Containment

class RangeContainer:
    def __init__(self, ranges):
        self.ranges = ranges

    def __contains__(self, num):
        return any(start <= num <= end for start, end in self.ranges)

ranges = RangeContainer([(1, 5), (10, 15), (20, 25)])
print(3 in ranges)   # True
print(8 in ranges)  # False
print(22 in ranges) # True

This efficiently handles discontinuous ranges without storing every possible value.

Performance Optimization

For large collections with frequent membership tests, use a set internally:

class OptimizedContainer:
    def __init__(self, items):
        self.items_set = set(items)

    def __contains__(self, item):
        return item in self.items_set

large_data = OptimizedContainer(range(1_000_000))
print(999999 in large_data)  # True - O(1) lookup
print(-1 in large_data)      # False - O(1) lookup

Fallback Behavior

If you don’t implement __contains__, Python tries __iter__ first, then falls back to __getitem__ with sequential index access. This is much slower for large collections.

See Also