L'opérateur in en Python détermine si un élément est présent dans une collection. Pour les objets personnalisés, afin de supporter la construction x in your_obj, il est nécessaire d'implémenter la méthode __contains__. S'il n'existe pas, l'interpréteur tentera d'itérer l'objet à l'aide de __iter__ ou __getitem__, mais le comportement et l'efficacité peuvent varier.
Exemple :
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 l'on ne réalise que __iter__ (ou même seulement __getitem__), alors in fonctionnera, mais de manière moins efficace et parfois complètement différente de ce qui est attendu.
À noter : si la collection est énorme et que la vérification est réalisée de manière naïve (par exemple, en parcourant toute la liste), cela peut poser des problèmes de performance. Pour des vérifications rapides, on utilise par exemple des ensembles.
Est-il suffisant d'implémenter uniquement
__iter__ou seulement__getitem__pour un fonctionnement correct de l'opérateurin? Comment le comportement changera-t-il ?
Réponse:
__contains__, Python essaiera de parcourir les éléments en utilisant __iter__ (s'il existe) ou __getitem__ (en commençant à l'indice 0 jusqu'à ce qu'une exception IndexError soit levée).Exemple :
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
Histoire
Dans un projet, un conteneur personnalisé pour stocker des entités a seulement redéfini
__iter__, oubliant d'implémenter__contains__. L'opérateurina commencé à fonctionner non seulement lentement (des lags étaient perceptibles pour de grandes collections), mais aussi à échouer avec des erreurs mystérieuses, lorsque l'itérateur jetait par erreur des exceptions qui n'étaient pas de type StopIteration.
Histoire
Pour une classe où les éléments étaient calculés "à la volée" par indice, le développeur n'a implémenté que
__getitem__. En tentant de vérifierx in objavec un grand x, de longues boucles sont apparues et même des erreurs de mémoire — carinvérifie tous les indices dans l'ordre croissant, jusqu'à ce qu'il rencontre un IndexError.
Histoire
Dans un des projets, un dictionnaire personnalisé a été réalisé, qui s'appuyait uniquement sur
__iter__pourin. Cela a conduit à ce que la recherche pour 100 000 clés prenne des secondes contre des millisecondes pour un dict standard (où__contains__est implémenté efficacement).