¿Qué significa exactamente “iterable” en Python?

Primero quiero aclarar, NO estoy preguntando qué es “iterador”.

Así es como se define el término “iterable” en el documento de Python:

iterable
Un objeto capaz de devolver a sus miembros uno a la vez. Los ejemplos de iterables incluyen todos los tipos de secuencia (como list, str y tuple) y algunos tipos que no son de secuencia como dict, objetos de archivo y objetos de cualquier clase que defina con un método __iter __ () o __getitem __ () . Los iterables se pueden usar en un bucle for y en muchos otros lugares donde se necesita una secuencia (zip (), map (), …). Cuando un objeto iterable se pasa como un argumento a la función incorporada iter (), devuelve un iterador para el objeto. Este iterador es bueno para una pasada sobre el conjunto de valores. Al usar iterables, generalmente no es necesario llamar iter () o tratar con objetos iteradores. La instrucción for lo hace automáticamente, creando una variable temporal sin nombre para mantener el iterador durante el ciclo. Ver también iterador, secuencia y generador.

Como otras personas sugirieron , el uso de isinstance(e, collections.Iterable) es la forma más pythonica de verificar si un objeto es iterable.
Así que hice algunas pruebas con Python 3.4.3:

 from collections.abc import Iterable class MyTrain: def __getitem__(self, index): if index > 3: raise IndexError("that's enough!") return index for name in MyTrain(): print(name) # 0, 1, 2, 3 print(isinstance(MyTrain(), Iterable)) # False 

El resultado es bastante extraño: MyTrain ha definido el método __getitem__ , pero no se considera un objeto iterable, sin mencionar que es capaz de devolver un número a la vez.

Luego __iter__ __getitem__ y agregué el método __iter__ :

 from collections.abc import Iterable class MyTrain: def __iter__(self): print("__iter__ called") pass print(isinstance(MyTrain(), Iterable)) # True for name in MyTrain(): print(name) # TypeError: iter() returned non-iterator of type 'NoneType' 

Ahora se considera como un objeto iterable “verdadero” a pesar de que no puede producir nada mientras se está iterando.

Entonces, ¿entendí mal algo o la documentación es incorrecta?

Creo que el punto de confusión aquí es que, aunque la implementación de __getitem__ le permite iterar sobre un objeto, no es parte de la interfaz definida por Iterable .

Las clases base abstractas permiten una forma de subclase virtual, donde las clases que implementan los métodos especificados (en el caso de Iterable , solo __iter__ ) son consideradas por isinstance y issubclass como subclases de los ABC, incluso si no se heredan explícitamente de ellos . Sin embargo, no verifica si la implementación del método realmente funciona , solo si se ha proporcionado o no.

Para obtener más información, consulte PEP-3119 , que introdujo el ABC.


el uso de isinstance(e, collections.Iterable) es la forma más pythonica de verificar si un objeto es iterable

Estoy en desacuerdo; Usaría tipografía de pato e intentaría iterar sobre el objeto . Si el objeto no es iterable, se generará un TypeError , que puede capturar en su función si desea tratar con entradas no iterables, o permitir que se filtre hasta la persona que llama, de lo contrario. Esto explica completamente por qué el objeto ha decidido implementar la iteración, y simplemente descubre si lo hace o no en el momento más apropiado.


Para agregar un poco más, creo que los documentos que ha citado son un poco confusos. Para citar los documentos de iter , que quizás aclare esto:

el objeto debe ser un objeto de colección que admita el protocolo de iteración (el __iter__() ), o debe admitir el protocolo de secuencia (el método __getitem__() con argumentos enteros comenzando en 0 ).

Esto deja en claro que, aunque ambos protocolos hacen que el objeto sea iterable, solo uno es el “protocolo de iteración” real, y esto es lo que las isinstance(thing, Iterable) . Por lo tanto, podemos concluir que una forma de verificar “las cosas por las que se puede repetir” en el caso más general sería:

 isinstance(thing, (Iterable, Sequence)) 

aunque esto también requiere que implemente __len__ junto con __getitem__ a la Sequence “virtualmente subclase” .

Es un iterable. Sin embargo, no has heredado de abc.Iterable , así que, naturalmente, Python no informará que desciende de esa clase. Las dos cosas, ser un iterable y descender de esa clase base, son bastante separadas.

Iterable es algo (colección de cualquier cosa) que permite algún tipo de iteración en sus elementos. Pero, ¿cuál es la forma genérica de iteración en python? Eso es usar – in palabra clave, que usa el método __iter__ de un objeto. Por lo tanto, en ese sentido, cualquier objeto que defina __iter__ se puede usar con in y es un iterable.

Por lo tanto, la forma más común de verificar si un objeto es iterable es si un objeto es esto, (Sí, sé implícitamente que eso es lo que está sucediendo también en el caso de isinstance , debido a clases virtuales)

 hasattr(train, '__iter__') 

porque de acuerdo con la tipificación de pato, nos preocupa el comportamiento proporcionado por un objeto en lugar de su ancestro.

Si tiene una implementación defectuosa de __iter__ eso no significa que el objeto no sea iterable, solo significa que tiene un error en su código.

Nota: – Los objetos que no definen __iter__ todavía pueden ser iterables en sentido general, al usar algún otro método, es solo que no se pueden usar con palabras clave. Por ejemplo: – NumberList instancia de NumberList es iterable sobre each método, pero no es iterable en el sentido de python.

 class NumberList: def __init__(self, values): self.values = values def each(self): return self.values