hasNext dans les itérateurs Python?

Réponses:

107

Non, une telle méthode n'existe pas. La fin de l'itération est indiquée par une exception. Consultez la documentation .

Avakar
la source
71
"Il est plus facile de demander pardon que la permission."
119
"Il est plus facile de demander pardon que la permission.": Vérifier si un itérateur a un élément suivant ne demande pas la permission. Il existe des situations dans lesquelles vous souhaitez tester l'existence d'un élément suivant sans le consommer. J'accepterais la solution try catch s'il y avait une unnext()méthode pour remettre le premier élément après avoir vérifié qu'il existe en appelant next().
Giorgio
15
@Giorgio, il n'y a aucun moyen de savoir si un autre élément existe sans exécuter le code qui le génère (vous ne savez pas si le générateur s'exécutera yieldou non). Il n'est bien sûr pas difficile d'écrire un adaptateur qui stocke le résultat de next()et fournit has_next()et move_next().
avakar
5
La même idée pourrait être utilisée pour implémenter la hasNext()méthode (pour produire, mettre en cache et renvoyer true en cas de succès, ou renvoyer false en cas d'échec). Ensuite , à la fois hasNext()et next()dépendrait d'un sous - jacent commun getNext()méthode et élément mis en cache. Je ne vois vraiment pas pourquoi next()ne devrait pas être dans la bibliothèque standard s'il est si facile d'implémenter un adaptateur qui le fournit.
Giorgio
3
@LarsH: Vous voulez dire par exemple un itérateur qui lit un fichier qui peut être modifié en lisant? Je suis d'accord que cela peut être un problème (qui affecte n'importe quelle bibliothèque next()et hasNext()méthode, pas seulement une bibliothèque Python hypothétique). Alors oui, next()et cela hasNext()devient délicat si le contenu du flux analysé dépend du moment où les éléments sont lus.
Giorgio
239

Il existe une alternative à l' StopIterationutilisation de next(iterator, default_value).

Par exemple:

>>> a = iter('hi')
>>> print next(a, None)
h
>>> print next(a, None)
i
>>> print next(a, None)
None

Ainsi, vous pouvez détecter pour Noneou toute autre valeur pré-spécifiée pour la fin de l'itérateur si vous ne voulez pas la manière d'exception.

Derrick Zhang
la source
70
si vous utilisez None comme "sentinelle", assurez-vous que votre itérateur n'a pas de Nones. vous pouvez également faire sentinel = object()et next(iterator, sentinel)et tester avec is.
sam boosalis
1
suivant @samboosalis, je préférerais utiliser un unittest.mock.sentinelobjet intégré qui vous permet d'écrire un explicite next(a, sentinel.END_OF_ITERATION), puisif next(...) == sentinel.END_OF_ITERATION
ClementWalter
c'est plus joli que l'exception
datdinhquoc
Le problème est que, de cette façon, vous CONSUMEZ également la valeur suivante de l'itérateur. hasNext en Java ne consomme pas la valeur suivante.
Alan Franzoni
39

Si vous avez vraiment besoin d' une has-nextfonctionnalité (parce que vous ne faites que transcrire fidèlement un algorithme à partir d'une implémentation de référence en Java, par exemple, ou parce que vous écrivez un prototype qui devra être facilement transcrit en Java une fois terminé), il est facile de obtenez-le avec une petite classe wrapper. Par exemple:

class hn_wrapper(object):
  def __init__(self, it):
    self.it = iter(it)
    self._hasnext = None
  def __iter__(self): return self
  def next(self):
    if self._hasnext:
      result = self._thenext
    else:
      result = next(self.it)
    self._hasnext = None
    return result
  def hasnext(self):
    if self._hasnext is None:
      try: self._thenext = next(self.it)
      except StopIteration: self._hasnext = False
      else: self._hasnext = True
    return self._hasnext

maintenant quelque chose comme

x = hn_wrapper('ciao')
while x.hasnext(): print next(x)

émet

c
i
a
o

comme demandé.

Notez que l'utilisation de next(sel.it)comme élément intégré nécessite Python 2.6 ou supérieur; si vous utilisez une ancienne version de Python, utilisez à la self.it.next()place (et de la même manière next(x)dans l'exemple d'utilisation). [[Vous pourriez raisonnablement penser que cette note est redondante, puisque Python 2.6 existe depuis plus d'un an maintenant - mais plus souvent qu'autrement lorsque j'utilise les fonctionnalités de Python 2.6 dans une réponse, certains commentateurs ou autres se sentent obligés de signaler qu'il s'agit de fonctionnalités 2.6, j'essaye donc de prévenir de tels commentaires pour une fois ;-)]]

Alex Martelli
la source
9
"transcrire fidèlement un algorithme à partir d'une implémentation de référence en Java" est la pire raison d'avoir besoin d'une has_nextméthode. La conception de Python rend impossible, par exemple, d'utiliser filterpour vérifier si un tableau contient un élément correspondant à un prédicat donné. L'arrogance et la myopie de la communauté Python sont stupéfiantes.
Jonathan Cast
belle réponse, je copie ceci pour l'illustration d'un modèle de conception tiré du code Java
madtyn
Je suis avec Python3 et ce code me donneTypeError: iter() returned non-iterator
madtyn
1
@JonathanCast je ne suis pas sûr de suivre. En Python, vous utiliseriez généralement mapet anyau lieu de filter, mais vous pouvez utiliser SENTINEL = object(); next(filter(predicate, arr), SENTINEL) is not SENTINELou oublier a SENTINELet simplement utiliser try: exceptet attraper le StopIteration.
juanpa.arrivillaga
13

En plus de toutes les mentions de StopIteration, la boucle Python "for" fait simplement ce que vous voulez:

>>> it = iter("hello")
>>> for i in it:
...     print i
...
h
e
l
l
o
Brian Clapper
la source
7

Essayez la méthode __length_hint __ () à partir de n'importe quel objet itérateur:

iter(...).__length_hint__() > 0
juj
la source
5
Je me suis toujours demandé pourquoi diable python a toutes ces méthodes __ xxx __? Ils semblent si laids.
mP.
6
Question légitime! Habituellement, c'est la syntaxe des méthodes exposées par une fonction intégrée (par exemple, len, appelle en fait len ). Une telle fonction intégrée n'existe pas pour length_hint, mais il s'agit en fait d'une proposition en attente (PEP424).
fulmicoton
1
@mP. ces fonctions sont là, car elles sont parfois nécessaires. Ils sont intentionnellement laids, car ils sont considérés comme une méthode de dernier recours: si vous les utilisez, vous savez que vous faites quelque chose de non pythonique et potentiellement dangereux (qui peut également cesser de fonctionner à tout moment).
Arne Babenhauserheide
Comme __init__et __main__? Imho, c'est un peu le bordel, peu importe que vous essayez de le justifier.
user1363990
5

hasNextse traduit quelque peu par l' StopIterationexception, par exemple:

>>> it = iter("hello")
>>> it.next()
'h'
>>> it.next()
'e'
>>> it.next()
'l'
>>> it.next()
'l'
>>> it.next()
'o'
>>> it.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
Miku
la source
4

Vous pouvez teel'itérateur à l'aide de, itertools.teeet vérifier StopIterationsur l'itérateur teed.

sykora
la source
3

Non. Le concept le plus similaire est probablement une exception StopIteration.

James Thompson
la source
10
Quel Python utilise des exceptions pour le flux de contrôle? Cela semble assez naïf.
mP.
5
À droite: les exceptions doivent être utilisées pour gérer les erreurs et non pour définir le flux normal de contrôle.
Giorgio
1

Le cas d'utilisation qui m'a conduit à rechercher ceci est le suivant

def setfrom(self,f):
    """Set from iterable f"""
    fi = iter(f)
    for i in range(self.n):
        try:
            x = next(fi)
        except StopIteration:
            fi = iter(f)
            x = next(fi)
        self.a[i] = x 

où hasnext () est disponible, on pourrait faire

def setfrom(self,f):
    """Set from iterable f"""
    fi = iter(f)
    for i in range(self.n):
        if not hasnext(fi):
            fi = iter(f) # restart
        self.a[i] = next(fi)

ce qui pour moi est plus propre. De toute évidence, vous pouvez contourner les problèmes en définissant des classes d'utilitaires, mais ce qui se passe alors, c'est que vous avez une prolifération de vingt solutions de contournement presque équivalentes différentes, chacune avec leurs bizarreries, et si vous souhaitez réutiliser du code qui utilise différentes solutions de contournement, vous devez soit avoir plusieurs quasi-équivalents dans votre application unique, ou parcourir et réécrire du code pour utiliser la même approche. La maxime «faites-le une fois et faites-le bien» échoue gravement.

De plus, l'itérateur lui-même a besoin d'une vérification interne 'hasnext' pour s'exécuter pour voir s'il a besoin de lever une exception. Cette vérification interne est ensuite masquée afin qu'elle doive être testée en essayant d'obtenir un élément, en interceptant l'exception et en exécutant le gestionnaire s'il est levé. Il s'agit de cacher l'OMI inutile.

John Allsup
la source
1
Pour ce cas d'utilisation, vous pouvez utiliser itertools.cycle
eaglebrain
0

La méthode suggérée est StopIteration . S'il vous plaît voir l'exemple Fibonacci de tutorialspoint

#!usr/bin/python3

import sys
def fibonacci(n): #generator function
   a, b, counter = 0, 1, 0
   while True:
      if (counter > n): 
         return
      yield a
      a, b = b, a + b
      counter += 1
f = fibonacci(5) #f is iterator object

while True:
   try:
      print (next(f), end=" ")
   except StopIteration:
      sys.exit()
Ramin Darvishov
la source
-2

La façon dont j'ai résolu mon problème est de garder le décompte du nombre d'objets itérés jusqu'à présent. Je voulais parcourir un ensemble en utilisant des appels à une méthode d'instance. Puisque je connaissais la longueur de l'ensemble et le nombre d'articles comptés jusqu'à présent, j'avais effectivement unhasNext méthode.

Une version simple de mon code:

class Iterator:
    # s is a string, say
    def __init__(self, s):
        self.s = set(list(s))
        self.done = False
        self.iter = iter(s)
        self.charCount = 0

    def next(self):
        if self.done:
            return None
        self.char = next(self.iter)
        self.charCount += 1
        self.done = (self.charCount < len(self.s))
        return self.char

    def hasMore(self):
        return not self.done

Bien sûr, l'exemple est un jouet, mais vous voyez l'idée. Cela ne fonctionnera pas dans les cas où il n'y a aucun moyen d'obtenir la longueur de l'itérable, comme un générateur, etc.

forumulator
la source