¿Cómo puedo hacer que los generadores / iteradores evalúen como Falso cuando están agotados?

Otros objetos vacíos en Python se evalúan como Falso. ¿Cómo puedo hacer que los iteradores / generadores también lo hagan?

Guido no quiere que los generadores e iteradores se comporten de esa manera.

Los objetos son verdaderos por defecto. Pueden ser falsos solo si definen __len__ que devuelve cero o __nonzero__ que devuelve False (este último se llama __bool__ en Py3.x).

Puede agregar uno de esos métodos a un iterador personalizado, pero no coincide con la intención de Guido. Rechazó agregar __len__ a los iteradores donde se conoce la próxima longitud. Así es como conseguimos __length_hint__ en su lugar.

Por lo tanto, la única forma de saber si un iterador está vacío es invocar next () y ver si genera StopIteration .

En ASPN, creo que hay algunas recetas que utilizan esta técnica para el envoltorio lookahead. Si se recupera un valor, se guarda en la próxima llamada siguiente ().

Por defecto, todos los objetos en Python se evalúan como True . Para admitir evaluaciones False , la clase del objeto debe tener un método __len__ ( 0 -> False ) o un método __nonzero__ ( False -> False ). Nota: __nonzero__ ==> __bool__ en Python 3.x.

Debido a que el protocolo del iterador se mantiene intencionalmente simple, y debido a que hay muchos tipos de iteradores / generadores que no pueden saber si hay más valores para producir antes de intentar producirlos, la evaluación True / False no es parte del protocolo del iterador .

Si realmente quieres este comportamiento, debes proporcionarlo tú mismo. Una forma es envolver el generador / iterador en una clase que proporcione la funcionalidad faltante.

Tenga en cuenta que este código solo se evalúa como False después de que se haya levantado StopIteration .

Como beneficio adicional, este código funciona para las pitones 2.4+

 try: next except NameError: # doesn't show up until python 2.6 def next(iter): return iter.next() Empty = object() class Boolean_Iterator(object): """Adds the abilities True/False tests: True means there /may/ be items still remaining to be used """ def __init__(self, iterator): self._iter = iter(iterator) self._alive = True def __iter__(self): return self def __next__(self): try: result = next(self._iter) except StopIteration: self._alive = False raise return result next = __next__ # python 2.x def __bool__(self): return self._alive __nonzero__ = __bool__ # python 2.x 

Si también desea un comportamiento de anticipación (o mirada), este código hará el truco (se evalúa como False antes de que se StopIteration ):

 try: next except NameError: # doesn't show up until python 2.6 def next(iter): return iter.next() Empty = object() class Iterator(object): """Adds the abilities True/False tests: True means there are items still remaining to be used peek(): get the next item without removing it from the sequence """ def __init__(self, iterator): self._iter = iter(iterator) self._peek = Empty self.peek() def __next__(self): peek, self._peek = self._peek, Empty self.peek() if peek is not Empty: return peek raise StopIteration next = __next__ # python 2.x def __bool__(self): return self._peek is not Empty __nonzero__ = __bool__ # python 2.x def peek(self): if self._peek is not Empty: return self._peek self._peek = next(self._iter, Empty) return self._peek 

Tenga en cuenta que el comportamiento de lectura no es apropiado cuando el tiempo del iterador / generador subyacente es relevante para sus valores producidos.

También tenga en cuenta que el código de terceros, y posiblemente el estándar, puede depender de que los iteradores / generadores siempre se evalúen como True . Si quieres echar un vistazo sin bool, elimina los métodos __nonzero__ y __bool__ .

una ‘cosa vacía’ no es automáticamente un iterador. los contenedores pueden estar vacíos o no, y usted puede obtener iteradores sobre los contenedores, pero esos iteradores no fallan cuando están agotados.

Un buen ejemplo de por qué los iteradores no se convierten en falsey es sys.stdin . El problema de hacer que sys.stdin falsey cuando llega al final de la entrada es que no hay forma de saber realmente si ha llegado al final de dicha secuencia sin intentar consumirla. La principal razón para querer que un iterador sea falsey sería “echar un vistazo” para ver si obtener el siguiente elemento sería válido; pero para sys.stdin , eso obviamente no es práctico.

aquí hay otro ejemplo

 (x for x in xrange(1000) if random.randrange(0, 2)) 

no hay forma de saber si este generador devolverá más números sin hacer un montón de trabajo, en realidad tienes que averiguar cuál será el siguiente valor.

La solución es obtener el siguiente valor del iterador. Si está vacío, su bucle se cerrará o obtendrá una excepción de StopIteration si no está en un bucle.