134 votes

Comment implémenter __iter__(self) pour un objet conteneur (Python)

J'ai écrit un objet conteneur personnalisé.

Selon cette page J'ai besoin d'implémenter cette méthode sur mon objet :

__iter__(self)

Cependant, en suivant le lien vers Types d'itérateurs dans le manuel de référence de Python, aucun exemple n'est donné sur la manière d'implémenter le vôtre.

Quelqu'un peut-il afficher un extrait (ou un lien vers une ressource) qui montre comment procéder ?

Le conteneur que je suis en train d'écrire est une carte (c'est-à-dire qu'il stocke les valeurs par des clés uniques). Les dicts peuvent être itérés comme ceci :

for k, v in mydict.items()

Dans ce cas, je dois pouvoir renvoyer deux éléments (un tuple ?) dans l'itérateur. La manière d'implémenter un tel itérateur n'est toujours pas claire (malgré les nombreuses réponses qui ont été aimablement fournies). Quelqu'un pourrait-il m'éclairer sur la manière d'implémenter un itérateur pour un objet conteneur de type map ? (c'est-à-dire une classe personnalisée qui agit comme un dict) ?

148voto

mikerobi Points 10461

Je devrais normalement utiliser une fonction de générateur. Chaque fois que vous utilisez une instruction yield, elle ajoute un élément à la séquence.

Ce qui suit va créer un itérateur qui donne cinq, puis chaque élément de some_list.

def __iter__(self):
   yield 5
   yield from some_list

Pré-3.3, yield from n'existait pas, donc tu devais le faire :

def __iter__(self):
   yield 5
   for x in some_list:
      yield x

1 votes

Faut-il lever le StopIteration ? Comment cela permet-il de distinguer le moment où il faut s'arrêter ?

1 votes

@JonathanLeaders Lorsque tous les éléments dans some_list ont été cédés.

1 votes

En fait, dans ce cas d'utilisation, vous n'avez besoin d'activer StopIteration que si vous souhaitez arrêter de fournir des valeurs. avant some_list est épuisé.

32voto

Muhammad Alkarouri Points 8463

Une autre option est d'hériter de la classe de base abstraite appropriée du module `collections' comme documenté aquí .

Dans le cas où le conteneur est son propre itérateur, vous pouvez hériter de collections.Iterator . Il suffit d'implémenter le next méthode alors.

Un exemple :

>>> from collections import Iterator
>>> class MyContainer(Iterator):
...     def __init__(self, *data):
...         self.data = list(data)
...     def next(self):
...         if not self.data:
...             raise StopIteration
...         return self.data.pop()
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
4.0
3
two
1

Pendant que vous regardez le collections envisagez d'hériter du module Sequence , Mapping ou une autre classe de base abstraite si cela est plus approprié. Voici un exemple pour une Sequence sous-classe :

>>> from collections import Sequence
>>> class MyContainer(Sequence):
...     def __init__(self, *data):
...         self.data = list(data)
...     def __getitem__(self, index):
...         return self.data[index]
...     def __len__(self):
...         return len(self.data)
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
1
two
3
4.0

NB : Merci à Glenn Maynard d'avoir attiré mon attention sur la nécessité de clarifier la différence entre les itérateurs d'une part et les conteneurs qui sont des itérables plutôt que des itérateurs d'autre part.

14 votes

Ne mélangez pas les objets itérables et les itérateurs - vous ne voulez pas hériter d'Iterator pour un objet itérable qui n'est pas lui-même un itérateur (par exemple, un conteneur).

0 votes

@Glenn : Vous avez raison de dire qu'un conteneur typique n'est pas un itérateur. J'ai juste suivi la question, qui mentionne les types d'itérateurs. Je pense qu'il est plus approprié d'hériter d'une option plus appropriée, comme je l'ai dit à la fin de la réponse. Je vais clarifier ce point dans la réponse.

0 votes

Il semble que l'utilisation de .pop() pour mettre en œuvre la fonction suivante entraîne quelques effets indésirables. Premièrement, l'itérateur renvoie les éléments de l'objet itérable dans l'ordre inverse, et deuxièmement, l'objet itérable est épuisé au fur et à mesure de son itération. Existe-t-il une autre approche pratique ?

17voto

mouad Points 21520

Généralement __iter__() retourne simplement self si vous avez déjà défini la méthode next() (objet générateur) :

Voici un exemple factice de générateur :

class Test(object):

    def __init__(self, data):
       self.data = data

    def next(self):
        if not self.data:
           raise StopIteration
        return self.data.pop()

    def __iter__(self):
        return self

mais __iter__() peut également être utilisé comme ceci : http://mail.python.org/pipermail/tutor/2006-January/044455.html

9 votes

C'est ce qu'on fait pour les classes d'itérateurs, mais la question porte sur les objets conteneurs.

2 votes

Est-ce toujours vrai dans Python 3 et plus ?

0 votes

La méthode "next" doit avoir deux caractères de soulignement avant et après son nom.

12voto

Squirrelsama Points 1333

Si votre objet contient un ensemble de données auxquelles vous voulez lier l'iter de votre objet, vous pouvez tricher et le faire :

>>> class foo:
    def __init__(self, *params):
           self.data = params
    def __iter__(self):
        if hasattr(self.data[0], "__iter__"):
            return self.data[0].__iter__()
        return self.data.__iter__()
>>> d=foo(6,7,3,8, "ads", 6)
>>> for i in d:
    print i
6
7
3
8
ads
6

2 votes

Au lieu de vérifier hasattr utiliser try/except AttributeError

7voto

Juan A. Navarro Points 1768

Pour répondre à la question sur mappings : vous avez fourni __iter__ doit itérer sur les clés de la cartographie. L'exemple suivant est un exemple simple qui crée un mappage x -> x * x et travaille sur Python3 en étendant le mapping ABC.

import collections.abc

class MyMap(collections.abc.Mapping):
    def __init__(self, n):
        self.n = n

    def __getitem__(self, key): # given a key, return it's value
        if 0 <= key < self.n:
            return key * key
        else:
            raise KeyError('Invalid key')

    def __iter__(self): # iterate over all keys
        for x in range(self.n):
            yield x

    def __len__(self):
        return self.n

m = MyMap(5)
for k, v in m.items():
    print(k, '->', v)
# 0 -> 0
# 1 -> 1
# 2 -> 4
# 3 -> 9
# 4 -> 16

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X