Métodos que toman iteradores en lugar de iterables

Con respecto a los iteradores e iterables (solo mi observación y corríjame si me equivoco):

  • La mayoría de los constructores (de tipo arrayish) toman iteradores como constructores de masa
  • Los iteradores se hacen explícitamente; o usando x in x for....
  • Muchos métodos (en su mayoría, itertools ) devuelven iteradores (porque su trabajo es iterar).
  • Métodos que toman iterables toman iteradores. ¿Es esto cierto en todos los casos?
  • Métodos que toman iteradores no lo toman (revertir no es cierto)
  • El único método que toma explícitamente un iterador parece ser el next(..

Preguntas:

El lenguaje alrededor de iteradores e iterables es un poco confuso. La principal confusión proviene del término “iterable”, que puede o no ser un superconjunto de “iterador”, dependiendo de cómo se esté utilizando.

Así es como clasificaría las cosas:

Un iterable es cualquier objeto sobre el que se puede iterar. Es decir, tiene un __iter__() que devuelve un iterador, o es indexable con enteros (generando una excepción IndexError cuando están fuera de rango), lo que permite a Python crear un iterador para él automáticamente. Esta es una categoría muy amplia.

Un iterador es un objeto que sigue el protocolo del iterador. Tiene un __next__() (escrito a next en Python 2) que produce el siguiente elemento, o genera la excepción StopIteration si no hay más valores disponibles. Un iterador también debe tener un __iter__() que se devuelva a sí mismo, por lo que todos los iteradores también son iterables (ya que cumplen con la definición de “iterable” que se indica más arriba).

Un iterador no iterador es cualquier iterable que no sea un iterador. Esto es a menudo lo que la gente quiere decir cuando usan el término “iterable” en contraste con “iterador”. Un término mejor en muchos contextos podría ser “secuencia”, pero eso es un poco más específico (algunos objetos que no tienen secuencia son iterables sin iteradores, como diccionarios que permiten la iteración sobre sus claves). La característica importante de esta categoría de objetos es que puede iterarlos varias veces, y los iteradores funcionan de manera independiente.

Así que para tratar de responder a sus preguntas específicas:

Rara vez hay una buena razón para que una función requiera un iterador específicamente. Por lo general, se puede hacer que las funciones funcionen igual de bien con cualquier tipo de argumento iterable, ya sea llamando a iter() en el argumento para obtener un iterador, o usando un bucle for que crea el iterador entre bambalinas.

Lo contrario es diferente. Si una función requiere un iterador no iterador, es posible que deba repetir el argumento varias veces para que un iterador no funcione correctamente. Sin embargo, las funciones en la biblioteca estándar de Python (y las incorporaciones) rara vez tienen tal limitación. Si necesitan iterar varias veces en un argumento iterable, a menudo lo vuelcan en un tipo de secuencia (por ejemplo, una lista) al comienzo, si no es una secuencia ya.

Muchas funciones devuelven iteradores. Todos los objetos del generador son iteradores, por ejemplo (tanto los que devuelven las funciones del generador como los creados con expresiones del generador). Los objetos de archivo también son iteradores (aunque violan un poco el protocolo del iterador, ya que puede reiniciarlos después de que se hayan agotado utilizando su método seek() ). Y todas las funciones y tipos en el módulo itertools devuelven iteradores, pero también algunas incorporaciones como map() (en Python 3).

La función next() es ciertamente inusual ya que específicamente requiere un iterador. Esto se debe a que se define como parte del protocolo de iteración en sí. Es exactamente equivalente a llamar al __next__() en el iterador, más fácil de leer. También tiene un formulario de dos argumentos que suprime la excepción StopIteration que, de lo contrario, se generaría si el iterador se agota (en su lugar, devuelve el argumento default ).