Comprendre l’itération et l’adhésion de Python : un guide des méthodes magiques __contains__ et __iter__

Un guide des méthodes magiques __contains__ et __iter__

mehreen-getting-started-with-Pythons-__iter__-__conatins__-methods-1 Comprendre l'itération et l'adhésion de Python : un guide des méthodes magiques __contains__ et __iter__ NEWS
Image par auteur

Si vous êtes nouveau sur Python, vous avez peut-être rencontré les termes « itération » et « adhésion » et je me demandais ce qu’ils voulaient dire. Ces concepts sont fondamentaux pour comprendre comment Python gère les collections de données, telles que les listes, les tuples et les dictionnaires. Python utilise des méthodes spéciales de dunder pour activer ces fonctionnalités.

Mais que sont exactement les méthodes dunder ? Les méthodes Dunder/Magic sont des méthodes spéciales en Python qui commencent et se terminent par un double trait de soulignement, d’où le nom « dunder ». Ils sont utilisés pour implémenter divers protocoles et peuvent être utilisés pour effectuer un large éventail de tâches, telles que la vérification de l’appartenance, l’itération sur des éléments, etc. Dans cet article, nous nous concentrerons sur deux des méthodes de dunder les plus importantes : __contient__ et __iter__. Alors, commençons.

Comprendre les boucles pythoniques avec la méthode Iter

Considérons une implémentation de base d’un répertoire de fichiers utilisant des classes Python comme suit :

class File:
	def __init__(self, file_path: str) -> None:
    	    self.file_path = file_path
   	 
class Directory:
	def __init__(self, files: List[File]) -> None:
    	    self._files = files

Un code simple dans lequel le répertoire a un paramètre d’instance qui contient une liste d’objets File. Maintenant, si nous voulons parcourir l’objet répertoire, nous devrions pouvoir utiliser une boucle for comme suit :

directory = Directory(
	files=[File(f"file_{i}") for i in range(10)]
)
for _file in directory:
	print(_file)

Nous initialisons un objet répertoire avec dix fichiers nommés aléatoirement et utilisons une boucle for pour parcourir chaque élément. Assez simple, mais oups ! Vous obtenez un message d’erreur : TypeError : l’objet ‘Répertoire’ n’est pas itérable.

Qu’est ce qui ne s’est pas bien passé? ​​Eh bien, notre classe Directory n’est pas configurée pour être parcourue en boucle. En Python, pour qu’un objet de classe devienne itérable, il doit implémenter le __iter__ méthode Dunder. Tous les itérables en Python comme List, Dictionaries et Set implémentent cette fonctionnalité afin que nous puissions les utiliser en boucle.

Ainsi, pour rendre notre objet Directory itérable, nous devons créer un itérateur. Considérez un itérateur comme un assistant qui nous donne les éléments un par un lorsque nous les demandons. Par exemple, lorsque nous parcourons une liste, l’objet itérateur nous fournira l’élément suivant à chaque itération jusqu’à ce que nous atteignions la fin de la boucle. C’est simplement ainsi qu’un itérateur est défini et implémenté en Python.

En Python, un itérateur doit savoir comment fournir l’élément suivant dans une séquence. Pour ce faire, il utilise une méthode appelée __suivant__. Lorsqu’il n’y a plus d’objets à donner, il émet un signal spécial appelé Arrêter l’itération pour dire: « Hé, nous avons fini ici. » Dans le cas d’une itération infinie, on n’élève pas le Arrêter l’itération exception.

Créons une classe itérateur pour notre répertoire. Il prendra la liste des fichiers comme argument et implémentera la méthode suivante pour nous donner le fichier suivant dans la séquence. Il garde une trace de la position actuelle à l’aide d’un index. La mise en œuvre se présente comme suit :

class FileIterator:
    def __init__(self, files: List[File]) -> None:
        self.files = files
        self._index = 0
    
    def __next__(self):
        if self._index >= len(self.files):
        	raise StopIteration
        value = self.files[self._index]
        self._index += 1
        return value

Nous initialisons une valeur d’index à 0 et acceptons les fichiers comme argument d’initialisation. Le __suivant__ La méthode vérifie si l’index déborde. Si c’est le cas, cela soulève un Arrêter l’itération exception pour signaler la fin de l’itération. Sinon, il renvoie le fichier à l’index actuel et passe au suivant en incrémentant l’index. Ce processus se poursuit jusqu’à ce que tous les fichiers aient été itérés.

Cependant, nous n’avons pas encore fini ! Nous n’avons toujours pas implémenté la méthode iter. La méthode iter doit renvoyer un objet itérateur. Maintenant que nous avons implémenté la classe FileIterator, nous pouvons enfin passer à la méthode iter.

class Directory:
    def __init__(self, files: List[File]) -> None:
        self._files = files
    
    def __iter__(self):
        return FileIterator(self._files)

La méthode iter initialise simplement un objet FileIterator avec sa liste de fichiers et renvoie l’objet itérateur. C’est tout ce qu’il faut ! Avec cette implémentation, nous pouvons désormais parcourir notre structure de répertoire en utilisant les boucles de Python. Voyons-le en action :


directory = Directory(
	files=[File(f"file_{i}") for i in range(10)]
)
for _file in directory:
	print(_file, end=", ")

# Output: file_0, file_1, file_2, file_3, file_4, file_5, file_6, file_7, file_8, file_9,

La boucle for appelle en interne le __iter__ méthode pour afficher ce résultat. Bien que cela fonctionne, vous pourriez toujours être confus quant au fonctionnement sous-jacent de l’itérateur en Python. Pour mieux le comprendre, utilisons une boucle while pour implémenter manuellement le même mécanisme.

directory = Directory(
	files=[File(f"file_{i}") for i in range(10)]
)

iterator = iter(directory)
while True:
    try:
        # Get the next item if available. Will raise StopIteration error if no item is left.
        item = next(iterator)   
        print(item, end=', ')
    except StopIteration as e:
        break   # Catch error and exit the while loop

# Output: file_0, file_1, file_2, file_3, file_4, file_5, file_6, file_7, file_8, file_9,

Nous invoquons la fonction iter sur l’objet répertoire pour acquérir le FileIterator. Ensuite, nous utilisons manuellement l’opérateur next pour appeler la méthode dunder suivante sur l’objet FileIterator. Nous gérons l’exception StopIteration pour terminer gracieusement la boucle while une fois que tous les éléments ont été épuisés. Comme prévu, nous avons obtenu le même résultat qu’avant !

Test d’appartenance avec la méthode Contains

Il s’agit d’un cas d’utilisation assez courant pour vérifier l’existence d’un élément dans une collection d’objets. Par exemple, dans notre exemple ci-dessus, nous devrons vérifier assez souvent si un fichier existe dans un répertoire. Python simplifie donc les choses syntaxiquement en utilisant l’opérateur « in ».

print(0 in [1,2,3,4,5]) # False
print(1 in [1,2,3,4,5]) # True

Ceux-ci sont principalement utilisés avec des expressions conditionnelles et des évaluations. Mais que se passe-t-il si nous essayons cela avec notre exemple d’annuaire ?

print("file_1" in directory)  # False
print("file_12" in directory) # False

Les deux nous donnent False, ce qui est incorrect ! Pourquoi? Pour vérifier l’adhésion, nous souhaitons implémenter le __contient__ méthode Dunder. Lorsqu’il n’est pas implémenté, Python revient à utiliser le __iter__ et évalue chaque élément avec l’opérateur ==. Dans notre cas, il parcourra chaque élément et vérifiera si le « fichier_1 » La chaîne correspond à n’importe quel objet File de la liste. Puisque nous comparons une chaîne à des objets File personnalisés, aucun des objets ne correspond, ce qui entraîne une évaluation False.

Pour résoudre ce problème, nous devons implémenter le __contient__ méthode dunder dans notre classe Directory.

class Directory:
    def __init__(self, files: List[File]) -> None:
        self._files = files
    
    def __iter__(self):
        return FileIterator(self._files)
    
    def __contains__(self, item):
        for _file in self._files:
        	# Check if file_path matches the item being checked
        	if item == _file.file_path:
            	return True
    	return False

Ici, nous modifions la fonctionnalité pour parcourir chaque objet et faire correspondre le chemin_fichier de l’objet File avec la chaîne transmise à la fonction. Maintenant, si nous exécutons le même code pour vérifier l’existence, nous obtenons le résultat correct !

directory = Directory(
	files=[File(f"file_{i}") for i in range(10)]
)

print("file_1" in directory)	# True
print("file_12" in directory) # False

Emballer

Et c’est tout! À l’aide de notre exemple simple de structure de répertoires, nous avons construit un simple itérateur et vérificateur d’adhésion pour comprendre le fonctionnement interne des boucles Pythonic. Nous voyons assez souvent de telles décisions de conception et implémentations dans le code au niveau de la production et, à l’aide de cet exemple concret, nous avons passé en revue les concepts intégraux derrière les méthodes __iter__ et __contains__. Continuez à pratiquer ces techniques pour renforcer votre compréhension et devenir un programmeur Python plus compétent !

Kanwal Mehreen Kanwal est un ingénieur en apprentissage automatique et un rédacteur technique passionné par la science des données et l’intersection de l’IA et de la médecine. Elle a co-écrit l’ebook « Maximiser la productivité avec ChatGPT ». En tant que Google Generation Scholar 2022 pour l’APAC, elle défend la diversité et l’excellence académique. Elle est également reconnue comme boursière Teradata Diversity in Tech, boursière Mitacs Globalink Research et boursière Harvard WeCode. Kanwal est un ardent défenseur du changement, ayant fondé FEMCodes pour autonomiser les femmes dans les domaines STEM.

Source