Comment faire la différence entre un itérateur et un itérable?

en Python l'interface d'un itérable est un sous-ensemble de iterator interface . Ceci a l'avantage que, dans de nombreux cas, ils peuvent être traités de la même manière. Cependant, il y a une différence sémantique importante entre les deux, puisque pour un itérable __iter__ renvoie un nouvel objet itérateur et pas seulement self . Comment puis-je tester qu'un itérable est vraiment un itérateur et non un itérateur? Conceptuellement, je comprends itérables d'être des collections, tandis qu'un iterator ne gère que l'itération (c'est-à-dire garde la trace de la position) mais n'est pas une collection elle-même.

la différence est par exemple importante quand on veut boucler plusieurs fois. Si un itérateur est donné alors la deuxième boucle ne fonctionnera pas puisque l'itérateur était déjà utilisé et soulève directement StopIteration .

Il est tentant de test pour un next la méthode, mais cela semble dangereux et quelque chose de mauvais. Dois-je juste vérifier que le la deuxième boucle est vide?

Est-il possible de faire un tel test dans un plus pythonic? Je sais que ça ressemble à un cas classique de LBYL contre L'EAFP, donc peut-être que je devrais abandonner? Ou ai-je raté quelque chose?

Edit: S. Lott dit dans sa réponse ci-dessous que c'est d'abord un problème de vouloir faire plusieurs passages sur l'itérateur, et que l'on ne devrait pas le faire, en premier lieu. Cependant, dans mon cas, les données sont très grand et en fonction de la situation doit être transmis plusieurs fois pour le traitement des données (il n'y a absolument aucun moyen de contourner cela).

l'itérable est également fourni par l'utilisateur, et pour les situations où un seul passage est suffisant, il fonctionnera avec un itérateur (par exemple créé par un générateur pour la simplicité). Mais il serait bien de se prémunir contre le cas où un utilisateur ne fournit qu'un itérateur lorsque plusieurs passes sont nécessaires.

Edit 2: En fait, C'est un très bel exemple pour classes de base abstraites . Les méthodes __iter__ dans un itérateur et un itérable ont le même nom mais sont sematiquement différentes! Donc hasattr est inutile, mais isinstance fournit une solution propre.

13
demandé sur nikow 2009-04-02 13:56:55

4 réponses

'iterator' if obj is iter(obj) else 'iterable'
13
répondu vartec 2009-04-02 10:11:29

cependant, il y a une différence sémantique importante entre les deux...

pas vraiment sémantique ou importante. Ils sont tous les deux itérables -- ils travaillent tous les deux avec un énoncé pour.

la différence est par exemple importante quand on veut boucler plusieurs fois.

quand est-ce que ça se présente? Vous devrez être plus précis. Dans les rares cas où vous avez besoin pour faire deux passages à travers une collection itérable, Il ya souvent de meilleurs algorithmes.

par exemple, disons que vous traitez une liste. Vous pouvez itérer à travers une liste Tout ce que vous voulez. Pourquoi t'es-tu emmêlé avec un itérateur au lieu de l'itérable? Bon, ça n'a pas fonctionné.

OK, en voilà un. Vous lisez un fichier en deux passages, et vous devez savoir comment réinitialiser l'itérable. Dans ce cas, c'est un fichier, et seek est nécessaire; ou fermer et rouvrir. Qui se sent de dégueulasse. Vous pouvez readlines pour obtenir une liste qui permet deux passes sans complexité. Ce n'est donc pas nécessaire.

attendez, et si on avait un fichier si gros qu'on ne peut pas tout lire dans la mémoire? Et, pour des raisons obscures, nous ne pouvons pas chercher non plus. Alors, quoi?

maintenant, nous sommes sur le nitty-gritty de deux passes. Au premier passage, nous avons accumulé quelque chose. Un index ou d'un résumé ou de quelque chose. Un index contient tous les fichiers données. Un résumé, souvent, une restructuration des données. Avec un petit changement de "résumé" de "restructurer", nous avons conservé les données du fichier dans la nouvelle structure. Dans les deux cas, nous n'avons pas besoin du fichier, nous pouvons utiliser l'index ou le sommaire.

tous les algorithmes" à deux passes " peuvent être changés en une passe de l'itérateur original ou itérable et une seconde passe d'une structure de données différente.

ce n'est ni LYBL ni EAFP. C'est une conception d'algorithme. Vous pas besoin de réinitialiser un itérateur ... YAGNI.


modifier

voici un exemple de problème itérateur/itérable. C'est juste un algorithme mal conçu.

it = iter(xrange(3))
for i in it: print i,; #prints 1,2,3 
for i in it: print i,; #prints nothing

c'est trivial.

it = range(3)
for i in it: print i
for i in it: print i

Le "plusieurs fois en parallèle" est trivialement fixe. Écrivez une API qui nécessite un itérable. Et quand quelqu'un refuse de lire la documentation de L'API ou refuse de la suivre après l'avoir lue, leur truc casse. Comme il se doit.

le "nice pour se prémunir contre le cas où un utilisateur fournit seulement un itérateur quand plusieurs passes sont nécessaires" sont deux exemples de personnes folles écrivant du code qui casse notre API simple.

si quelqu'un est assez fou pour lire la plupart (mais pas la totalité du DOC API) et fournir un itérateur quand un itérable était requis , vous devez trouver cette personne et lui apprendre (1) Comment lire toute la documentation de L'API et (2) suivre la documentation de l'API.

la question des" sauvegardes " n'est pas très réaliste. Ces programmeurs fous sont remarquablement rares. Et dans les quelques cas quand il se produit, vous savez qui ils sont et pouvez les aider.


Modifier 2

Le "nous avons pour lire la même structure plusieurs fois " algorithmes sont un problème fondamental.

ne faites pas ça.

for element in someBigIterable:
    function1( element )
for element in someBigIterable:
    function2( element )
...

faites ceci, à la place.

for element in someBigIterable:
    function1( element )
    function2( element )
    ...

Ou, pensez à quelque chose comme ça.

for element in someBigIterable:
    for f in ( function1, function2, function3, ... ):
        f( element )

dans la plupart des cas, ce genre de" pivot " de vos algorithmes résulte en un programme qui pourrait être plus facile à optimiser et pourrait être une nette amélioration de la performance.

4
répondu S.Lott 2011-11-30 17:35:54
import itertools

def process(iterable):
    work_iter, backup_iter= itertools.tee(iterable)

    for item in work_iter:
        # bla bla
        if need_to_startover():
            for another_item in backup_iter:

cette fichue machine temporelle que Raymond a empruntée à Guido ...

2
répondu tzot 2009-04-02 23:24:19

à cause de python's duck typing,

tout objet est itérable s'il définit les méthodes next() et __iter__() .

si l'objet lui-même n'a pas la méthode next() , le __iter__() peut retourner n'importe quel objet, qui a une next() méthode

vous pouvez vous référer à cette question pour voir Itérabilité en Python

0
répondu Lakshman Prasad 2017-05-23 11:54:16