ProgrammationDéveloppeur Python intermédiaire

Comment fonctionne l'opérateur 'in' pour les objets personnalisés en Python ? Que faut-il implémenter dans la classe pour que l'expression 'x in your_obj' fonctionne ? Comment éviter les problèmes de performance et les erreurs inattendues ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

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.

Question piégeuse.

Est-il suffisant d'implémenter uniquement __iter__ ou seulement __getitem__ pour un fonctionnement correct de l'opérateur in ? Comment le comportement changera-t-il ?

Réponse:

  • En l'absence de __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).
  • Ce comportement est moins efficace et peut provoquer des boucles infinies ou des exceptions si les méthodes sont mal implémentées.

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

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet.


Histoire

Dans un projet, un conteneur personnalisé pour stocker des entités a seulement redéfini __iter__, oubliant d'implémenter __contains__. L'opérateur in a 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érifier x in obj avec un grand x, de longues boucles sont apparues et même des erreurs de mémoire — car in vé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__ pour in. 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).