En Python, ¿cómo determino si un objeto es iterable?

1085

¿Hay un método como isiterable? La única solución que he encontrado hasta ahora es llamar

hasattr(myObj, '__iter__')

Pero no estoy seguro de cuán infalible es esto.

willem
fuente
18
__getitem__también es suficiente para hacer que un objeto sea iterable
Kos
44
FWIW: iter(myObj)tiene éxito si isinstance(myObj, dict), así que si estás viendo myObjuna secuencia que podría ser una dicto una sola dict, tendrás éxito en ambos casos. Una sutileza que es importante si quieres saber qué es una secuencia y qué no. (en Python 2)
Ben Mosher
77
__getitem__también es suficiente para hacer que un objeto sea iterable ... si comienza en índice cero .
Carlos A. Gómez

Respuestas:

28

He estado estudiando este problema bastante últimamente. Basado en eso, mi conclusión es que hoy en día este es el mejor enfoque:

from collections.abc import Iterable   # drop `.abc` with Python 2.7 or lower

def iterable(obj):
    return isinstance(obj, Iterable)

Lo anterior ya se ha recomendado anteriormente, pero el consenso general ha sido que usar iter()sería mejor:

def iterable(obj):
    try:
        iter(obj)
    except Exception:
        return False
    else:
        return True

También hemos utilizado iter()nuestro código para este propósito, pero últimamente comencé a sentirme cada vez más molesto por los objetos que solo se __getitem__consideran iterables. Hay razones válidas para tener __getitem__un objeto no iterable y con ellas el código anterior no funciona bien. Como ejemplo de la vida real, podemos usar Faker . El código anterior informa que es iterable pero que en realidad intenta iterarlo causa un AttributeError(probado con Faker 4.0.2):

>>> from faker import Faker
>>> fake = Faker()
>>> iter(fake)    # No exception, must be iterable
<iterator object at 0x7f1c71db58d0>
>>> list(fake)    # Ooops
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/.../site-packages/faker/proxy.py", line 59, in __getitem__
    return self._factory_map[locale.replace('-', '_')]
AttributeError: 'int' object has no attribute 'replace'

Si lo usáramos insinstance(), no consideraríamos accidentalmente que las instancias de Faker (o cualquier otro objeto que solo tenga __getitem__) sean iterables:

>>> from collections.abc import Iterable
>>> from faker import Faker
>>> isinstance(Faker(), Iterable)
False

Las respuestas anteriores comentaron que el uso iter()es más seguro ya que la antigua forma de implementar la iteración en Python se basaba __getitem__y el isinstance()enfoque no lo detectaría. Esto puede haber sido cierto con las versiones antiguas de Python, pero basado en mis pruebas bastante exhaustivas isinstance()funciona muy bien hoy en día. El único caso en el isinstance()que no funcionó pero iter()sí fue con el UserDictuso de Python 2. Si eso es relevante, es posible utilizarlo isinstance(item, (Iterable, UserDict))para cubrirlo.

Pekka Klärck
fuente
1
También typing.Dictse considera iterable iter(Dict)pero list(Dict)falla con error TypeError: Parameters to generic types must be types. Got 0.. Como se esperaba isinstance(Dict, Iterable)devuelve falso.
Pekka Klärck
1
Llegué a la misma conclusión, pero por diferentes razones. El uso iterprovocó que parte de nuestro código que usaba "almacenamiento previo en caché" se ralentizara innecesariamente. Si el __iter__código es lento, también lo hará iter... en cualquier momento que desee ver si algo es iterable.
thorwhalen
842
  1. Verificando __iter__trabajos en tipos de secuencia, pero fallaría, por ejemplo, en cadenas en Python 2 . También me gustaría saber la respuesta correcta, hasta entonces, aquí hay una posibilidad (que también funcionaría en cadenas):

    from __future__ import print_function
    
    try:
        some_object_iterator = iter(some_object)
    except TypeError as te:
        print(some_object, 'is not iterable')

    Las itercomprobaciones incorporadas para el __iter__método o en el caso de cadenas del __getitem__método.

  2. Otro enfoque pitónico general es asumir un iterable, luego fallar con gracia si no funciona en el objeto dado. El glosario de Python:

    Estilo de programación pitónica que determina el tipo de un objeto mediante la inspección de su método o firma de atributo en lugar de mediante una relación explícita con algún objeto de tipo ("Si parece un pato y grazna como un pato , debe ser un pato "). en lugar de tipos específicos, el código bien diseñado mejora su flexibilidad al permitir la sustitución polimórfica. Duck-typing evita las pruebas con type () o isinstance (). En cambio, generalmente emplea el estilo de programación EAFP (más fácil de pedir perdón que permiso).

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
  3. El collectionsmódulo proporciona algunas clases base abstractas, que permiten preguntar a clases o instancias si proporcionan una funcionalidad particular, por ejemplo:

    from collections.abc import Iterable
    
    if isinstance(e, Iterable):
        # e is iterable

    Sin embargo, esto no verifica las clases que son iterables __getitem__.

miku
fuente
34
[e for e in my_object]puede generar una excepción por otros motivos, my_objectes decir, es indefinido o posibles errores en la my_objectimplementación.
Nick Dandoulakis
37
Una cadena es una secuencia ( isinstance('', Sequence) == True) y como cualquier secuencia es iterable ( isinstance('', Iterable)). Aunque hasattr('', '__iter__') == Falsey podría ser confuso.
jfs
82
Si my_objectes muy grande (digamos, infinito itertools.count()) su comprensión de la lista tomará mucho tiempo / memoria. Es mejor hacer un generador, que nunca intentará construir una lista (potencialmente infinita).
Chris Lutz
14
¿Qué sucede si some_object arroja TypeError causado por otra razón (errores, etc.) también? ¿Cómo podemos distinguirlo del "Error de tipo no iterable"?
Shaung
54
Tenga en cuenta que en Python 3: hasattr(u"hello", '__iter__')regresaTrue
Carlos
573

Pato escribiendo

try:
    iterator = iter(theElement)
except TypeError:
    # not iterable
else:
    # iterable

# for obj in iterator:
#     pass

Comprobación de tipo

Use las clases base abstractas . Necesitan al menos Python 2.6 y funcionan solo para clases de estilo nuevo.

from collections.abc import Iterable   # import directly from collections for Python < 3.3

if isinstance(theElement, Iterable):
    # iterable
else:
    # not iterable

Sin embargo, iter()es un poco más confiable como se describe en la documentación :

La comprobación isinstance(obj, Iterable)detecta clases que están registradas como Iterable o que tienen un __iter__()método, pero no detecta clases que iteran con el __getitem__() método. La única forma confiable de determinar si un objeto es iterable es llamar iter(obj).

Georg Schölly
fuente
18
De "Fluent Python" de Luciano Ramalho: a partir de Python 3.4, la forma más precisa de verificar si un objeto x es iterable es llamar iter (x) y manejar una excepción TypeError si no lo es. Esto es más preciso que usar isinstance (x, abc.Iterable), porque iter (x) también considera el método getitem heredado , mientras que el Iterable ABC no.
RdB
En caso de que esté pensando "oh, simplemente en isinstance(x, (collections.Iterable, collections.Sequence))lugar de iter(x)", tenga en cuenta que esto todavía no detectará un objeto iterable que se implemente solo __getitem__pero no __len__. Usa iter(x)y atrapa la excepción.
Dale
Tu segunda respuesta no funciona. En PyUNO si lo hago iter(slide1), todo va bien, sin embargo los isinstance(slide1, Iterable)lanzamientos TypeError: issubclass() arg 1 must be a class.
Hola Ángel
@ Hola-Angel suena como un error en PyUNOObserve que su mensaje de error dice en issubclass()lugar de isinstance().
Georg Schölly
2
Llamar a iter () sobre un objeto puede ser una operación costosa (ver DataLoader en Pytorch, que bifurca / genera múltiples procesos en iter ()).
szali
126

Me gustaría arrojar un poco más de luz sobre la interacción de iter, __iter__y __getitem__y lo que sucede detrás de las cortinas. Armado con ese conocimiento, podrá comprender por qué lo mejor que puede hacer es

try:
    iter(maybe_iterable)
    print('iteration will probably work')
except TypeError:
    print('not iterable')

Primero enumeraré los hechos y luego haré un seguimiento con un recordatorio rápido de lo que sucede cuando empleas un forbucle en Python, seguido de una discusión para ilustrar los hechos.

Hechos

  1. Puede obtener un iterador de cualquier objeto ollamando iter(o)si al menos una de las siguientes condiciones es verdadera:

    a) otiene un __iter__método que devuelve un objeto iterador. Un iterador es cualquier objeto con un método __iter__y un __next__(Python 2 next:).

    b) otiene un __getitem__método.

  2. Verificar una instancia de Iterableo Sequence, o verificar el atributo __iter__no es suficiente.

  3. Si un objeto osolo se implementa __getitem__, pero no __iter__, iter(o)construirá un iterador que intente buscar elementos opor índice entero, comenzando en el índice 0. El iterador detectará cualquier IndexError(pero ningún otro error) que se genere y luego se levante StopIteration.

  4. En el sentido más general, no hay forma de verificar si el iterador devuelto por iteres sensato que no sea probarlo.

  5. Si se oimplementa un objeto __iter__, la iterfunción se asegurará de que el objeto devuelto por __iter__sea ​​un iterador. No hay comprobación de cordura si un objeto solo se implementa __getitem__.

  6. __iter__gana. Si un objeto oimplementa ambos __iter__y __getitem__, iter(o)llamará __iter__.

  7. Si desea que sus propios objetos sean iterables, implemente siempre el __iter__método.

for bucles

Para seguirlo, necesita comprender qué sucede cuando emplea un forbucle en Python. Si ya lo sabe, puede pasar directamente a la siguiente sección.

Cuando se usa for item in opara algún objeto iterable o, Python llama iter(o)y espera un objeto iterador como valor de retorno. Un iterador es cualquier objeto que implementa un método __next__(o nexten Python 2) y un __iter__método.

Por convención, el __iter__método de un iterador debe devolver el objeto en sí (es decir return self). Python luego llama nextal iterador hasta que StopIterationse eleva. Todo esto sucede implícitamente, pero la siguiente demostración lo hace visible:

import random

class DemoIterable(object):
    def __iter__(self):
        print('__iter__ called')
        return DemoIterator()

class DemoIterator(object):
    def __iter__(self):
        return self

    def __next__(self):
        print('__next__ called')
        r = random.randint(1, 10)
        if r == 5:
            print('raising StopIteration')
            raise StopIteration
        return r

Iteración sobre un DemoIterable:

>>> di = DemoIterable()
>>> for x in di:
...     print(x)
...
__iter__ called
__next__ called
9
__next__ called
8
__next__ called
10
__next__ called
3
__next__ called
10
__next__ called
raising StopIteration

Discusión e ilustraciones.

En los puntos 1 y 2: obtener un iterador y verificaciones poco confiables

Considere la siguiente clase:

class BasicIterable(object):
    def __getitem__(self, item):
        if item == 3:
            raise IndexError
        return item

Llamar itercon una instancia de BasicIterabledevolverá un iterador sin ningún problema porque se BasicIterableimplementa __getitem__.

>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>

Sin embargo, es importante tener en cuenta que bno tiene el __iter__atributo y no se considera una instancia de Iterableo Sequence:

>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False

Es por eso que Fluent Python de Luciano Ramalho recomienda llamar itery manejar el potencial TypeErrorcomo la forma más precisa de verificar si un objeto es iterable. Citando directamente del libro:

A partir de Python 3.4, la forma más precisa de verificar si un objeto xes iterable es llamar iter(x)y manejar una TypeErrorexcepción si no lo es. Esto es más preciso que usarlo isinstance(x, abc.Iterable), porque iter(x)también considera el __getitem__método heredado , mientras que el IterableABC no.

En el punto 3: iterar sobre objetos que solo proporcionan __getitem__, pero no__iter__

Iterando sobre una instancia de BasicIterabletrabajos como se esperaba: Python construye un iterador que intenta buscar elementos por índice, comenzando en cero, hasta que IndexErrorse genera un. El __getitem__método del objeto de demostración simplemente devuelve el itemque fue suministrado como argumento __getitem__(self, item)por el iterador devuelto por iter.

>>> b = BasicIterable()
>>> it = iter(b)
>>> next(it)
0
>>> next(it)
1
>>> next(it)
2
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Tenga en cuenta que el iterador se eleva StopIterationcuando no puede devolver el siguiente elemento y que el IndexErrorque se genera item == 3se maneja internamente. Esta es la razón por la cual recorrer un a BasicIterablecon un forbucle funciona como se esperaba:

>>> for x in b:
...     print(x)
...
0
1
2

Aquí hay otro ejemplo para llevar a casa el concepto de cómo el iterador regresó al iterintentar acceder a los elementos por índice. WrappedDictno hereda de dict, lo que significa que las instancias no tendrán un __iter__método.

class WrappedDict(object): # note: no inheritance from dict!
    def __init__(self, dic):
        self._dict = dic

    def __getitem__(self, item):
        try:
            return self._dict[item] # delegate to dict.__getitem__
        except KeyError:
            raise IndexError

Tenga en cuenta que las llamadas a __getitem__se delegan dict.__getitem__para las cuales la notación de corchetes es simplemente una abreviatura.

>>> w = WrappedDict({-1: 'not printed',
...                   0: 'hi', 1: 'StackOverflow', 2: '!',
...                   4: 'not printed', 
...                   'x': 'not printed'})
>>> for x in w:
...     print(x)
... 
hi
StackOverflow
!

En los puntos 4 y 5: iterbusca un iterador cuando llama__iter__ :

Cuando iter(o)se llama a un objeto o, iterse asegurará de que el valor de retorno de __iter__, si el método está presente, sea un iterador. Esto significa que el objeto devuelto debe implementar __next__(o nexten Python 2) y __iter__. iterno puede realizar ninguna comprobación de cordura para los objetos que solo proporcionan __getitem__, porque no tiene forma de verificar si los elementos del objeto son accesibles por índice entero.

class FailIterIterable(object):
    def __iter__(self):
        return object() # not an iterator

class FailGetitemIterable(object):
    def __getitem__(self, item):
        raise Exception

Tenga en cuenta que la construcción de un iterador a partir de FailIterIterableinstancias falla inmediatamente, mientras que la construcción de un iterador se realiza FailGetItemIterablecorrectamente, pero arrojará una excepción en la primera llamada a __next__.

>>> fii = FailIterIterable()
>>> iter(fii)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'object'
>>>
>>> fgi = FailGetitemIterable()
>>> it = iter(fgi)
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/iterdemo.py", line 42, in __getitem__
    raise Exception
Exception

En el punto 6: __iter__gana

Este es sencillo. Si un objeto implementa __iter__y __getitem__, iterllamará __iter__. Considere la siguiente clase

class IterWinsDemo(object):
    def __iter__(self):
        return iter(['__iter__', 'wins'])

    def __getitem__(self, item):
        return ['__getitem__', 'wins'][item]

y la salida al recorrer una instancia:

>>> iwd = IterWinsDemo()
>>> for x in iwd:
...     print(x)
...
__iter__
wins

En el punto 7: sus clases iterables deberían implementar __iter__

Puede preguntarse por qué la mayoría de las secuencias integradas, como listimplementar un __iter__método __getitem__, serían suficientes.

class WrappedList(object): # note: no inheritance from list!
    def __init__(self, lst):
        self._list = lst

    def __getitem__(self, item):
        return self._list[item]

Después de todo, la iteración sobre las instancias de la clase anterior, que delega llamadas __getitem__a list.__getitem__(usando la notación de corchetes), funcionará bien:

>>> wl = WrappedList(['A', 'B', 'C'])
>>> for x in wl:
...     print(x)
... 
A
B
C

Los motivos por los que deberían implementarse sus iterables personalizados __iter__son los siguientes:

  1. Si implementa __iter__, las instancias se considerarán iterables y isinstance(o, collections.abc.Iterable)volverán True.
  2. Si el objeto devuelto por __iter__no es un iterador, iterfallará inmediatamente y elevará a TypeError.
  3. El manejo especial de __getitem__existe por razones de compatibilidad con versiones anteriores. Citando nuevamente de Fluent Python:

Es por eso que cualquier secuencia de Python es iterable: todas se implementan __getitem__. De hecho, las secuencias estándar también se implementan __iter__, y las suyas también deberían hacerlo, porque el manejo especial de __getitem__existe por razones de compatibilidad con versiones anteriores y puede desaparecer en el futuro (aunque no está en desuso mientras escribo esto).

timgeb
fuente
Entonces, ¿es seguro definir un predicado is_iterableregresando Trueen el trybloque y Falseen el except TypeErrorbloque?
alancalvitti
Esta es una respuesta genial. Creo que resalta la naturaleza poco intuitiva y desafortunada del protocolo getitem. Nunca debería haberse agregado.
Neil G
31

Esto no es suficiente: el objeto devuelto por __iter__debe implementar el protocolo de iteración (es decir, el nextmétodo). Consulte la sección correspondiente en la documentación .

En Python, una buena práctica es "probar y ver" en lugar de "verificar".

jldupont
fuente
99
"pato escribiendo", creo? :)
willem
99
@willem: o "no pidas permiso sino perdón" ;-)
jldupont el
14
@willem Los estilos de "permiso" y "perdón" califican como tipeo de pato. Si preguntas qué puede hacer un objeto en lugar de lo que es , eso es escribir pato. Si usa introspección, eso es "permiso"; si solo intentas hacerlo y ves si funciona o no, eso es "perdón".
Mark Reed
22

En Python <= 2.5, no puede y no debe: iterable era una interfaz "informal".

Pero desde Python 2.6 y 3.0 puede aprovechar la nueva infraestructura ABC (clase base abstracta) junto con algunos ABC incorporados que están disponibles en el módulo de colecciones:

from collections import Iterable

class MyObject(object):
    pass

mo = MyObject()
print isinstance(mo, Iterable)
Iterable.register(MyObject)
print isinstance(mo, Iterable)

print isinstance("abc", Iterable)

Ahora, si esto es deseable o realmente funciona, es solo una cuestión de convenciones. Como puede ver, puede registrar un objeto no iterable como Iterable, y generará una excepción en tiempo de ejecución. Por lo tanto, isinstance adquiere un "nuevo" significado: solo comprueba la compatibilidad de tipos "declarados", que es una buena manera de hacerlo en Python.

Por otro lado, si su objeto no satisface la interfaz que necesita, ¿qué va a hacer? Tome el siguiente ejemplo:

from collections import Iterable
from traceback import print_exc

def check_and_raise(x):
    if not isinstance(x, Iterable):
        raise TypeError, "%s is not iterable" % x
    else:
        for i in x:
            print i

def just_iter(x):
    for i in x:
        print i


class NotIterable(object):
    pass

if __name__ == "__main__":
    try:
        check_and_raise(5)
    except:
        print_exc()
        print

    try:
        just_iter(5)
    except:
        print_exc()
        print

    try:
        Iterable.register(NotIterable)
        ni = NotIterable()
        check_and_raise(ni)
    except:
        print_exc()
        print

Si el objeto no satisface lo que espera, simplemente arroja un TypeError, pero si se ha registrado el ABC apropiado, su cheque no sirve. Por el contrario, si el __iter__método está disponible, Python reconocerá automáticamente el objeto de esa clase como Iterable.

Entonces, si solo espera un iterable, repítalo y olvídelo. Por otro lado, si necesita hacer cosas diferentes según el tipo de entrada, puede encontrar la infraestructura ABC bastante útil.

Alan Franzoni
fuente
13
no use bare except:en el código de ejemplo para principiantes. Promueve las malas prácticas.
jfs
JFS: No lo haría, pero necesitaba pasar por múltiples códigos de aumento de excepciones y no quería detectar la excepción específica ... Creo que el propósito de este código es bastante claro.
Alan Franzoni
21
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

No ejecute controles para ver si su pato realmente es un pato para ver si es iterable o no, trátelo como si lo fuera y reclame si no lo fue.

badp
fuente
3
Técnicamente, durante la iteración, su cómputo podría arrojar ay tirar TypeErroraquí, pero básicamente sí.
Chris Lutz
66
@willem: utilice timeit para realizar un punto de referencia. Las excepciones de Python son a menudo más rápidas que las declaraciones if. Pueden tomar un camino un poco más corto a través del intérprete.
S.Lott
2
@willem: IronPython tiene excepciones lentas (en comparación con CPython).
jfs
2
Un intento de trabajo: la declaración es realmente rápida. Entonces, si tiene pocas excepciones, try-except es rápido. Si espera muchas excepciones, "if" puede ser más rápido.
Arne Babenhauserheide
2
¿No debería capturarse el objeto de excepción agregando " as e" después en TypeErrorlugar de agregar " , e"?
HelloGoodbye
21

Desde Python 3.5 puede usar el módulo de mecanografía de la biblioteca estándar para cosas relacionadas con el tipo:

from typing import Iterable

...

if isinstance(my_item, Iterable):
    print(True)
Rotareti
fuente
18

La mejor solución que he encontrado hasta ahora:

hasattr(obj, '__contains__')

que básicamente verifica si el objeto implementa el inoperador.

Ventajas (ninguna de las otras soluciones tiene las tres):

  • es una expresión (funciona como una lambda , en oposición al intento ... excepto la variante)
  • es (debe ser) implementado por todos los iterables, incluidas las cadenas (en lugar de __iter__)
  • funciona en cualquier Python> = 2.5

Notas:

  • la filosofía de Python de "pedir perdón, no permiso" no funciona bien cuando, por ejemplo, en una lista tiene tanto iterables como no iterables y necesita tratar cada elemento de manera diferente de acuerdo con su tipo (tratar los iterables en try y non- iterables de excepción serían trabajar, pero que se vería a tope fea y engañosa)
  • Las soluciones a este problema que intentan iterar sobre el objeto (por ejemplo, [x para x en obj]) para verificar si es iterable pueden inducir penalizaciones de rendimiento significativas para iterables grandes (especialmente si solo necesita los primeros elementos del iterable, para ejemplo) y debe evitarse
Vlad
fuente
3
Bien, pero ¿por qué no usar el módulo de colecciones como se propone en stackoverflow.com/questions/1952464/… ? Me parece más expresivo.
Dave Abrahams
1
Es más corto (y no requiere importaciones adicionales) sin perder claridad: tener un método "contiene" se siente como una forma natural de verificar si algo es una colección de objetos.
Vlad
46
El hecho de que algo pueda contener algo no significa necesariamente que sea iterable. Por ejemplo, un usuario puede verificar si un punto está en un cubo 3D, pero ¿cómo iteraría a través de este objeto?
Casey Kuball
13
Esto es incorrecto. Un iterable en sí mismo no es compatible con contiene , al menos con Python 3.4.
Peter Shinners
15

Podrías probar esto:

def iterable(a):
    try:
        (x for x in a)
        return True
    except TypeError:
        return False

Si podemos hacer un generador que lo repita (pero nunca use el generador para que no ocupe espacio), es iterable. Parece una especie de "duh". ¿Por qué necesita determinar si una variable es iterable en primer lugar?

Chris Lutz
fuente
¿Qué hay de iterable(itertools.repeat(0))? :)
badp
55
@badp, (x for x in a)simplemente crea un generador, no realiza ninguna iteración en un.
catchmeifyoutry
55
¿Intentar es (x for x in a)exactamente equivalente a intentar iterator = iter(a)? ¿O hay algunos casos en que los dos son diferentes?
max
¿No es for _ in a: breakmás sencillo? ¿Es más lento?
Mr_and_Mrs_D
2
@Mr_and_Mrs_D eso es malo si el objeto probado es un iterador que se repitió después (será 1 elemento corto ya que su posición no se puede restablecer), la creación de generadores de basura no itera sobre el objeto ya que no se repite, aunque no estoy seguro de que aumentará al 100% un TypeError si no es iterable.
Tcll
13

Encontré una buena solución aquí :

isiterable = lambda obj: isinstance(obj, basestring) \
    or getattr(obj, '__iter__', False)
jbochi
fuente
11

Según el glosario de Python 2 , los iterables son

todos los tipos de secuencia (como list, stry tuple) y algunos tipos que no son de secuencia, como dicty filey 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 se pasa un objeto iterable como argumento a la función incorporada iter (), devuelve un iterador para el objeto.

Por supuesto, dado el estilo de codificación general para Python basado en el hecho de que es "más fácil pedir perdón que permiso", la expectativa general es usar

try:
    for i in object_in_question:
        do_something
except TypeError:
    do_something_for_non_iterable

Pero si necesita verificarlo explícitamente, puede probar un iterable por hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). Debe verificar ambos, porque los strs no tienen un __iter__método (al menos no en Python 2, en Python 3 sí) y porque los generatorobjetos no tienen un __getitem__método.

Anaphory
fuente
8

A menudo encuentro conveniente, dentro de mis scripts, definir una iterablefunción. (Ahora incorpora la simplificación sugerida de Alfe):

import collections

def iterable(obj):
    return isinstance(obj, collections.Iterable):

para que pueda probar si algún objeto es iterable en una forma muy legible

if iterable(obj):
    # act on iterable
else:
    # not iterable

como harías con la callablefunción

EDITAR: si tiene instalado numpy, simplemente puede hacer: from numpy import iterable, que es simplemente algo así como

def iterable(obj):
    try: iter(obj)
    except: return False
    return True

Si no tiene numpy, simplemente puede implementar este código o el anterior.

fmonegaglia
fuente
3
Cada vez que haces algo como if x: return True else: return False(con xser booleano) puedes escribir esto como return x. En tu caso return isinstance(…)sin ninguno if.
Alfe
Dado que reconoce que la solución de Alfe es mejor, ¿por qué no editó su respuesta para decir eso simplemente? En cambio, ahora tiene AMBAS versiones en su respuesta. Verbosidad innecesaria. Enviar una edición para arreglar esto.
ToolmakerSteve
2
Debería atrapar "TypeError" en la línea `except: return False`. Atrapar todo es un mal patrón.
Mariusz Jamro
Saber que. Traduje ese fragmento de código de la biblioteca NumPy, que usa la excepción genérica.
fmonegaglia
El hecho de que un código se tome de NumPy no significa que sea bueno ... patrón o no, el único momento en que se debe hacer todo es si está explícitamente manejando errores dentro de su programa.
Tcll
5

tiene una función incorporada como esa:

from pandas.util.testing import isiterable
Sören
fuente
Sin embargo, esto solo se ve si hay __iter__y no realmente le importan las secuencias y similares.
Ead
4

Siempre me he eludido por qué Python tiene callable(obj) -> boolpero no iterable(obj) -> bool...
seguramente es más fácil de hacer, hasattr(obj,'__call__')incluso si es más lento.

Como casi todas las respuestas recomiendan usar try/ except TypeError, donde las pruebas de excepciones generalmente se consideran una mala práctica en cualquier idioma, aquí hay una implementación deiterable(obj) -> bool me he aficionado y uso con frecuencia:

Por el bien de Python 2, usaré una lambda solo para ese aumento de rendimiento adicional ...
(en Python 3 no importa lo que use para definir la función, deftiene aproximadamente la misma velocidad que lambda)

iterable = lambda obj: hasattr(obj,'__iter__') or hasattr(obj,'__getitem__')

Tenga en cuenta que esta función se ejecuta más rápido para los objetos __iter__ya que no prueba__getitem__ .

La mayoría de los objetos iterables deben depender de __iter__dónde caen los objetos de casos especiales __getitem__, aunque cualquiera de ellos es necesario para que un objeto sea iterable.
(y como esto es estándar, también afecta a los objetos C)

Tcll
fuente
no proporciona un código de trabajo, y mucho menos hablar sobre el rendimiento de Python ... aunque esta respuesta fue realmente solo por conveniencia, como he visto varias veces aquí.
Tcll
3
def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

Esto dirá sí a todo tipo de objetos iterables, pero dirá no a las cadenas en Python 2 . (Eso es lo que quiero, por ejemplo, cuando una función recursiva podría tomar una cadena o un contenedor de cadenas. En esa situación, pedir perdón puede conducir a obfuscode, y es mejor pedir permiso primero).

import numpy

class Yes:
    def __iter__(self):
        yield 1;
        yield 2;
        yield 3;

class No:
    pass

class Nope:
    def __iter__(self):
        return 'nonsense'

assert is_iterable(Yes())
assert is_iterable(range(3))
assert is_iterable((1,2,3))   # tuple
assert is_iterable([1,2,3])   # list
assert is_iterable({1,2,3})   # set
assert is_iterable({1:'one', 2:'two', 3:'three'})   # dictionary
assert is_iterable(numpy.array([1,2,3]))
assert is_iterable(bytearray("not really a string", 'utf-8'))

assert not is_iterable(No())
assert not is_iterable(Nope())
assert not is_iterable("string")
assert not is_iterable(42)
assert not is_iterable(True)
assert not is_iterable(None)

Muchas otras estrategias aquí dirán sí a las cadenas. Úsalos si eso es lo que quieres.

import collections
import numpy

assert isinstance("string", collections.Iterable)
assert isinstance("string", collections.Sequence)
assert numpy.iterable("string")
assert iter("string")
assert hasattr("string", '__getitem__')

Nota: is_iterable () dirá sí a las cadenas de tipo bytesy bytearray.

  • bytes los objetos en Python 3 son iterables True == is_iterable(b"string") == is_iterable("string".encode('utf-8')) No hay tal tipo en Python 2.
  • bytearray los objetos en Python 2 y 3 son iterables True == is_iterable(bytearray(b"abc"))

El PO hasattr(x, '__iter__')enfoque va a decir que sí a las cadenas en Python 3 y no en Python 2 (no importa si ''o b''o u''). Gracias a @LuisMasuelli por notar que también te decepcionará __iter__.

Bob Stein
fuente
2

La forma más fácil, respetando el tipeo del pato de Python , es detectar el error (Python sabe perfectamente qué espera de un objeto para convertirse en un iterador):

class A(object):
    def __getitem__(self, item):
        return something

class B(object):
    def __iter__(self):
        # Return a compliant iterator. Just an example
        return iter([])

class C(object):
    def __iter__(self):
        # Return crap
        return 1

class D(object): pass

def iterable(obj):
    try:
        iter(obj)
        return True
    except:
        return False

assert iterable(A())
assert iterable(B())
assert iterable(C())
assert not iterable(D())

Notas :

  1. Es irrelevante la distinción de si el objeto no es iterable o tiene errores __iter__ se ha implementado , si el tipo de excepción es el mismo: de todos modos, no podrá iterar el objeto.
  2. Creo que entiendo su preocupación: ¿cómo callableexiste una verificación si también podría confiar en la escritura de pato para aumentar un AttributeErrorif __call__no está definido para mi objeto, pero ese no es el caso de la verificación iterable?

    No sé la respuesta, pero puede implementar la función que yo (y otros usuarios) le di, o simplemente detectar la excepción en su código (su implementación en esa parte será como la función que escribí, solo asegúrese de aislar el creación del iterador del resto del código para que pueda capturar la excepción y distinguirla de otra TypeError.

Luis Masuelli
fuente
1

La isiterablefunción en el siguiente código regresa Truesi el objeto es iterable. si no es retornableFalse

def isiterable(object_):
    return hasattr(type(object_), "__iter__")

ejemplo

fruits = ("apple", "banana", "peach")
isiterable(fruits) # returns True

num = 345
isiterable(num) # returns False

isiterable(str) # returns False because str type is type class and it's not iterable.

hello = "hello dude !"
isiterable(hello) # returns True because as you know string objects are iterable
Nómada
fuente
2
tantas respuestas detalladas arriba con muchos votos positivos y arrojas una respuesta inexplicable ... meh
Nrzonline
Por favor, no publique código desnudo. Incluya también una explicación de lo que está haciendo esto.
Jonathan Mee el
1

En lugar de verificar el __iter__atributo, puede verificar el __len__atributo, que se implementa en cada iteración incorporada de Python, incluidas las cadenas.

>>> hasattr(1, "__len__")
False
>>> hasattr(1.3, "__len__")
False
>>> hasattr("a", "__len__")
True
>>> hasattr([1,2,3], "__len__")
True
>>> hasattr({1,2}, "__len__")
True
>>> hasattr({"a":1}, "__len__")
True
>>> hasattr(("a", 1), "__len__")
True

Los objetos no iterables no implementarían esto por razones obvias. Sin embargo, no captura iterables definidos por el usuario que no lo implementan, ni las expresiones generadoras, que iterpueden manejar. Sin embargo, esto se puede hacer en una línea, y agregar una simple orcomprobación de expresión para los generadores solucionaría este problema. (Tenga en cuenta que escribir type(my_generator_expression) == generatorarrojaría un NameError. Consulte esta respuesta en su lugar).

Puede usar GeneratorType de tipos:

>>> import types
>>> types.GeneratorType
<class 'generator'>
>>> gen = (i for i in range(10))
>>> isinstance(gen, types.GeneratorType)
True

--- respuesta aceptada por utdemir

(Sin embargo, esto es útil para verificar si puede invocar lenel objeto).

DarthCadeus
fuente
desafortunadamente no todos los objetos iterables usan __len__... para este caso, generalmente es el uso incorrecto de calcular la distancia entre 2 objetos. donde obj.dist()podría ser fácilmente sustituido.
Tcll
Si. La mayoría de los iterables definidos por el usuario implementan iter y getitem pero no len. Sin embargo, los tipos incorporados sí lo hacen, y si desea verificar si puede llamar a la función len en él, verificar len es más seguro. Pero tienes razón.
DarthCadeus
0

Realmente no es "correcto", pero puede servir como verificación rápida de los tipos más comunes como cadenas, tuplas, flotadores, etc.

>>> '__iter__' in dir('sds')
True
>>> '__iter__' in dir(56)
False
>>> '__iter__' in dir([5,6,9,8])
True
>>> '__iter__' in dir({'jh':'ff'})
True
>>> '__iter__' in dir({'jh'})
True
>>> '__iter__' in dir(56.9865)
False
Jan Musil
fuente
0

Llegué un poco tarde a la fiesta, pero me hice esta pregunta y vi esto, luego pensé en una respuesta. No sé si alguien ya publicó esto. Pero esencialmente, he notado que todos los tipos iterables tienen __getitem __ () en su dict. Así es como verificaría si un objeto era iterable sin siquiera intentarlo. (Juego de palabras)

def is_attr(arg):
    return '__getitem__' in dir(arg)
lakam99
fuente
Desafortunadamente, esto no es confiable. Ejemplo
timgeb
1
Establecer objetos son otro contraejemplo.
Raymond Hettinger