El operador in en Python determina si un elemento está contenido en una colección. Para los objetos personalizados, para que se mantenga la construcción x in your_obj, es necesario implementar el método __contains__. Si no está presente, el intérprete intentará iterar sobre el objeto utilizando __iter__ o __getitem__, pero el comportamiento y la eficiencia pueden diferir.
Ejemplo:
class MyBag: def __init__(self, items): self.items = items def __contains__(self, value): return value in self.items bag = MyBag([1,2,3]) print(2 in bag) # True print(5 in bag) # False
Si se implementa solo __iter__ (o incluso solo __getitem__), in funcionará, pero de manera menos eficiente y a veces completamente diferente a lo esperado.
Nota: si la colección es enorme y la comprobación se implementa de manera ingenua (por ejemplo, mediante un bucle que recorre toda la lista), pueden surgir problemas de rendimiento. Para comprobaciones rápidas, se utilizan, por ejemplo, conjuntos.
¿Es suficiente implementar solo
__iter__o solo__getitem__para que el operadorinfuncione correctamente? ¿Cómo cambiará el comportamiento?
Respuesta:
__contains__, Python intentará recorrer los elementos utilizando __iter__ (si está presente) o __getitem__ (comenzando desde el índice 0, hasta que se produzca una excepción IndexError).Ejemplo:
class Weird: def __getitem__(self, idx): if idx < 3: return idx raise IndexError w = Weird() print(2 in w) # True print(5 in w) # False
Historia
En un proyecto, un contenedor personalizado para almacenar entidades sobreescribió solo
__iter__, olvidando implementar__contains__. El operadorincomenzó a funcionar no solo lentamente (con retrasos evidentes para colecciones grandes), sino que también falló repentinamente con errores misteriosos cuando el iterador arrojaba excepciones que no eran del tipo StopIteration.
Historia
Para una clase donde los elementos se calculaban "sobre la marcha" por índice, el desarrollador implementó solo
__getitem__. Al intentar verificarx in objcon un x grande, surgieron ciclos largos e incluso Out Of Memory — puesinverifica todos los índices en orden ascendente, hasta encontrar un IndexError.
Historia
En uno de los proyectos se implementó un diccionario personalizado que para
independía únicamente de__iter__. Esto llevó a que la búsqueda para 100,000 claves tardara segundos en comparación con milisegundos del dict estándar (donde__contains__está implementado de manera eficiente).