← Назад к вопросам
Зачем нужен __contains__ в итераторе Python?
1.7 Middle🔥 141 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Метод contains в Python
__contains__ — это магический метод который определяет поведение оператора in. Он используется для проверки принадлежности элемента к контейнеру.
Базовое определение
# __contains__ это то что вызывается при использовании 'in'
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) # Вызовет container.__contains__(3) → True
print(10 in container) # Вызовет container.__contains__(10) → False
print(5 not in container) # False
Как Python выбирает реализацию
Когда пишешь x in container, Python выполняет следующее:
- Если есть
__contains__— вызовет его (самый приоритетный) - Если нет
__contains__— попробует использовать__iter__(итеративный поиск) - Если нет обоих — попробует
__getitem__с индексами (0, 1, 2...) - Если ничего нет — ошибка TypeError
# Пример 1: с __contains__
class FastLookup:
def __init__(self, items):
self.items = set(items) # O(1) поиск
def __contains__(self, item):
return item in self.items # Быстро!
fast = FastLookup([1, 2, 3])
print(3 in fast) # O(1) время
# Пример 2: без __contains__, но есть __iter__
class SlowLookup:
def __init__(self, items):
self.items = items
def __iter__(self):
return iter(self.items)
slow = SlowLookup([1, 2, 3])
print(3 in slow) # Python итерирует и проверяет каждый элемент O(n)
Производительность: почему это важно
import time
data = list(range(1_000_000))
# Без оптимизации: проверка каждого элемента O(n)
class UnoptimizedContainer:
def __init__(self, items):
self.items = items
unopt = UnoptimizedContainer(data)
start = time.perf_counter()
for _ in range(100):
999_999 in unopt.items # Проверяет 1M элементов каждый раз
long_time = time.perf_counter() - start
# С оптимизацией: O(1) lookup
class OptimizedContainer:
def __init__(self, items):
self.items = set(items)
def __contains__(self, item):
return item in self.items
opt = OptimizedContainer(data)
start = time.perf_counter()
for _ in range(100):
999_999 in opt # Вызовет __contains__ → O(1)
fast_time = time.perf_counter() - start
print(f"Без оптимизации: {long_time:.3f}s")
print(f"С оптимизацией: {fast_time:.3f}s")
print(f"Быстрее в {long_time / fast_time:.0f}x раз!")
Результат: 50-500x ускорение просто добавив __contains__!
Практические примеры
1. Кастомный список с быстрым поиском
class FastList:
def __init__(self, items=None):
self.items = list(items) if items else []
self._set = set(items) if items else set()
def append(self, item):
self.items.append(item)
self._set.add(item)
def __contains__(self, item):
# Используем множество для O(1) поиска
return item in self._set
def __iter__(self):
return iter(self.items) # Порядок сохранён!
def __len__(self):
return len(self.items)
fast_list = FastList([1, 2, 3, 4, 5])
fast_list.append(6)
print(3 in fast_list) # O(1) благодаря __contains__
print(list(fast_list)) # [1, 2, 3, 4, 5, 6] порядок есть
2. Диапазон чисел
class Range:
def __init__(self, start, end):
self.start = start
self.end = end
def __contains__(self, item):
# Не хранит все числа в памяти! O(1) проверка
return self.start <= item < self.end
def __iter__(self):
return iter(range(self.start, self.end))
my_range = Range(0, 1_000_000_000) # Миллиард чисел!
print(500 in my_range) # O(1) и работает мгновенно
print(2_000_000 in my_range) # O(1)
# Не может итерировать мгновенно (список бы занял Гб памяти)
3. Граф для проверки наличия вершины
class Graph:
def __init__(self):
self.vertices = set()
self.edges = {}
def add_vertex(self, v):
self.vertices.add(v)
self.edges[v] = set()
def add_edge(self, v1, v2):
self.edges[v1].add(v2)
self.edges[v2].add(v1)
def __contains__(self, vertex):
# O(1) проверка наличия вершины
return vertex in self.vertices
def __iter__(self):
return iter(self.vertices)
graph = Graph()
graph.add_vertex('A')
graph.add_vertex('B')
graph.add_edge('A', 'B')
print('A' in graph) # True (быстро)
print('C' in graph) # False (быстро)
4. Объект с разрешенными значениями
class HTTPStatus:
ALLOWED_CODES = {200, 201, 204, 301, 302, 400, 401, 403, 404, 500, 502, 503}
def __contains__(self, code):
return code in self.ALLOWED_CODES
http = HTTPStatus()
if 200 in http:
print("Valid status code") # O(1) проверка
if 999 in http:
print("Invalid")
else:
print("Code 999 not allowed")
5. Доступ к атрибутам класса
class Person:
attributes = {'name', 'age', 'email', 'phone'}
def __init__(self, name, age):
self.name = name
self.age = age
def __contains__(self, attr):
return attr in self.attributes
person = Person("Alice", 30)
if 'name' in person:
print("Name is allowed attribute") # O(1)
if 'secret' in person:
print("Secret is allowed")
else:
print("Secret attribute not allowed") # O(1)
Разница: contains vs getitem
class WithGetItem:
def __init__(self, items):
self.items = items
def __getitem__(self, index):
return self.items[index]
# Без __contains__! Python будет перебирать индексы
obj = WithGetItem([1, 2, 3, 4, 5])
print(3 in obj) # Работает, но O(n): iterates через __getitem__
class WithContains:
def __init__(self, items):
self.items = items
def __contains__(self, item):
# Более эффективная реализация
return item in self.items
obj2 = WithContains([1, 2, 3, 4, 5])
print(3 in obj2) # O(n), но явно написано
Когда добавлять contains
ДОБАВЛЯЙ если:
- ✓ Часто проверяешь наличие элемента (
in) - ✓ Есть более эффективный способ чем линейный поиск
- ✓ Можешь кешировать данные (множество, индекс)
- ✓ Логика поиска сложная (диапазоны, условия)
НЕ ДОБАВЛЯЙ если:
- ❌ Уже есть
__iter__и линейный поиск OK - ❌ Контейнер очень маленький (< 10 элементов)
- ❌ Проверка используется редко
Вывод
__contains__ позволяет оптимизировать проверку принадлежности элемента. Это особенно важно для больших контейнеров, где можно использовать структуры данных (set, dict, sorted list) для O(1) или O(log n) поиска вместо O(n).