hasNext en los iteradores de Python?

Respuestas:

106

No, no existe tal método. El final de la iteración se indica mediante una excepción. Ver la documentación .

avakar
fuente
71
"Es más fácil pedir perdón que permiso".
118
"Es más fácil pedir perdón que permiso": comprobar si un iterador tiene un elemento siguiente no es pedir permiso. Hay situaciones en las que desea probar la existencia de un elemento siguiente sin consumirlo. Aceptaría la solución try catch si hubiera un unnext()método para volver a colocar el primer elemento después de haber comprobado que existe llamando next().
Giorgio
15
@Giorgio, no hay forma de saber si existe otro elemento sin ejecutar el código que lo genera (no sabes si el generador se ejecutará yieldo no). Por supuesto, no es difícil escribir un adaptador que almacene el resultado next()y proporcione has_next()y move_next().
avakar
55
La misma idea podría usarse para implementar el hasNext()método (para producir, almacenar en caché y devolver verdadero en caso de éxito o falso en caso de error). Entonces ambos hasNext()y next()dependerían de un getNext()método subyacente común y un elemento en caché. Realmente no veo por next()qué no debería estar en la biblioteca estándar si es tan fácil implementar un adaptador que lo proporciona.
Giorgio
3
@LarsH: ¿Te refieres, por ejemplo, a un iterador que lee de un archivo que se puede cambiar mientras se lee? Estoy de acuerdo en que esto puede ser un problema (que afecta a cualquier biblioteca que proporcione next()y hasNext()método, no solo una hipotética biblioteca de Python). Entonces sí, next()y se hasNext()vuelve complicado si el contenido de la secuencia que se escanea depende de cuándo se leen los elementos.
Giorgio el
239

Hay una alternativa al StopIterationuso next(iterator, default_value).

Por ejemplo:

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

Por lo tanto, puede detectar Noneu otro valor preespecificado para el final del iterador si no desea la forma de excepción.

Derrick Zhang
fuente
70
si usa None como "centinela", es mejor asegurarse de que su iterador no tenga Nones. también se puede hacer sentinel = object()y next(iterator, sentinel)y probar con is.
Sam Boosalis
1
siguiendo a @samboosalis, preferiría usar un unittest.mock.sentinelobjeto incorporado que le permita escribir un mensaje explícito next(a, sentinel.END_OF_ITERATION)y luegoif next(...) == sentinel.END_OF_ITERATION
ClementWalter
esto es más bonito que la excepción
datdinhquoc
El problema es que, de esta manera, también CONSUMES el siguiente valor del iterador. hasNext en Java no consume el siguiente valor.
Alan Franzoni
39

Si realmente necesita una has-nextfuncionalidad (porque solo está transcribiendo fielmente un algoritmo de una implementación de referencia en Java, por ejemplo, o porque está escribiendo un prototipo que deberá transcribirse fácilmente a Java cuando esté terminado), es fácil obténgalo con una pequeña clase de envoltura. Por ejemplo:

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

ahora algo como

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

emite

c
i
a
o

según sea necesario.

Tenga en cuenta que el uso de next(sel.it)como incorporado requiere Python 2.6 o superior; si está utilizando una versión anterior de Python, use self.it.next()en su lugar (y de manera similar para next(x)el uso de ejemplo). [[Puede pensar razonablemente que esta nota es redundante, ya que Python 2.6 ha existido por más de un año, pero la mayoría de las veces cuando uso las características de Python 2.6 en una respuesta, algunos comentaristas u otros se sienten obligados a señalar que son características 2.6, por lo que estoy tratando de evitar tales comentarios por una vez ;-)]]

Alex Martelli
fuente
9
"transcribir fielmente un algoritmo desde una implementación de referencia en Java" es la peor razón para necesitar un has_nextmétodo. El diseño de Python hace imposible, por ejemplo, usar filterpara verificar si una matriz contiene un elemento que coincide con un predicado dado. La arrogancia y la miopía de la comunidad de Python es asombrosa.
Jonathan Cast
respuesta agradable, estoy copiando esto para ilustrativa de algún patrón diseño tomado de código Java
madtyn
Estoy con Python3 y este código me daTypeError: iter() returned non-iterator
madtyn
1
@JonathanCast no estoy seguro de seguir. En Python, normalmente usaría mapy en anylugar de filter, pero podría usar SENTINEL = object(); next(filter(predicate, arr), SENTINEL) is not SENTINELu olvidar ay SENTINELsimplemente usar try: excepty capturar el StopIteration.
juanpa.arrivillaga
13

Además de todas las menciones de StopIteration, el bucle "for" de Python simplemente hace lo que desea:

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

Pruebe el método __length_hint __ () desde cualquier objeto iterador:

iter(...).__length_hint__() > 0
juj
fuente
55
Siempre me pregunté por qué Python tiene todos esos métodos __ xxx __. Parecen tan feos.
mP.
66
Pregunta legítima! Por lo general, es la sintaxis de los métodos que están expuestos por una función incorporada (por ejemplo, len, en realidad llama a len ). Dicha función integrada no existe para length_hint, pero en realidad es una propuesta pendiente (PEP424).
fulmicoton
1
@mP. estas funciones están ahí, porque a veces son necesarias. Son intencionalmente feos, porque se consideran como método de último recurso: si los usa, sabe que hace algo no pitónico y potencialmente peligroso (que también podría dejar de funcionar en cualquier momento).
Arne Babenhauserheide
Me gusta __init__y __main__? En mi humilde opinión, es un desastre, no importa si tratas de justificarlo.
user1363990
4

Puede teeusar el iterador itertools.tee, y verificar StopIterationen el iterador de teed.

sykora
fuente
3

No. El concepto más similar es probablemente una excepción StopIteration.

James Thompson
fuente
10
¿Qué Python usa excepciones para el flujo de control? Suena muy bien hecho.
mP.
55
Derecha: las excepciones deben usarse para manejar errores, no para definir el flujo normal de control.
Giorgio
1

El caso de uso que me llevó a buscar esto es el siguiente

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 

donde hasnext () está disponible, uno podría hacer

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)

que para mí es más limpio Obviamente, puede solucionar los problemas definiendo las clases de utilidad, pero lo que sucede es que tiene una proliferación de veinte soluciones alternativas casi equivalentes diferentes, cada una con sus peculiaridades, y si desea reutilizar el código que utiliza soluciones alternativas diferentes, debe tenga múltiples casi equivalentes en su única aplicación, o busque y reescriba el código para usar el mismo enfoque. La máxima de "hazlo una vez y hazlo bien" falla gravemente.

Además, el iterador en sí mismo necesita tener una verificación interna 'hasnext' para ejecutarse para ver si necesita generar una excepción. Esta comprobación interna se oculta, por lo que debe probarse intentando obtener un elemento, capturando la excepción y ejecutando el controlador si se produce. Esto es innecesario para ocultar IMO.

John Allsup
fuente
1
Para este caso de uso, puede usar itertools.cycle
eaglebrain
0

La forma sugerida es StopIteration . Por favor vea el ejemplo de Fibonacci desde 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
fuente
-2

La forma en que resolví mi problema es mantener el recuento de la cantidad de objetos iterados, hasta ahora. Quería iterar sobre un conjunto utilizando llamadas a un método de instancia. Como sabía la longitud del conjunto y la cantidad de elementos contados hasta ahora, efectivamente tuve un hasNextmétodo.

Una versión simple de mi código:

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

Por supuesto, el ejemplo es de juguete, pero se entiende la idea. Esto no funcionará en casos donde no hay forma de obtener la longitud del iterable, como un generador, etc.

forumulator
fuente