Pregunta En Python, ¿cómo puedo determinar si un objeto es iterable?


¿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.


802
2017-12-23 12:13


origen


Respuestas:


  1. Verificando para __iter__ funciona en tipos de secuencia, pero fallaría, p. instrumentos de cuerda en Python 2. Me gustaría saber la respuesta correcta también, hasta entonces, aquí hay una posibilidad (que también funcionaría en cadenas):

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

    los iter controles incorporados para el __iter__ método o en el caso de cadenas de la __getitem__ método.

  2. Otro acercamiento pythonic general es asumir un iterable, entonces fallar graciosamente si no trabaja en el objeto dado. El glosario de Python:

    Estilo de programación Pythonic que determina el tipo de un objeto mediante la inspección de su método o la firma del atributo en lugar de una relación explícita con algún tipo de objeto ("Si se ve como un pato y charlatanes como un pato, debe ser un pato. ") Al enfatizar las interfaces en lugar de los tipos específicos, el código bien diseñado mejora su flexibilidad al permitir la sustitución polimórfica. El tipado de pato evita las pruebas con type () o isinstance (). En cambio, normalmente emplea el estilo de programación EAFP (más fácil de pedir perdón).

    ...

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

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

    Sin embargo, esto no comprueba las clases que son iterables a través de __getitem__.


644
2017-12-23 12:16



Pato escribiendo

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

# for obj in iterator:
#     pass

Tipo de comprobación

Utilizar el Clases base abstractas. Necesitan al menos Python 2.6 y solo funcionan para clases de estilo nuevo.

import collections

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

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

Comprobación isinstance(obj, Iterable) detecta clases que son   registrado como Iterable o que tiene 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).


491
2017-12-23 12:53



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á entender por qué lo mejor que puede hacer es

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

Enumeraré los hechos primero y luego haré un seguimiento con un recordatorio rápido de lo que sucede cuando emplea un for loop en python, seguido de una discusión para ilustrar los hechos.

Hechos

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

    un) o tiene un __iter__ método que devuelve un objeto iterador. Un iterador es cualquier objeto con un __iter__ y un __next__ (Python 2: next) método.

    segundo) o tiene un __getitem__ método.

  2. Buscando una instancia de Iterable o Sequence, o buscando el atributo __iter__ no es suficiente.

  3. Si un objeto o solo implementa __getitem__, pero no __iter__, iter(o)construirá un iterador que intenta obtener elementos de o por índice entero, comenzando en el índice 0. El iterador captará cualquier IndexError (pero no otros errores) que se plantea y luego aumenta StopIteration sí mismo.

  4. En el sentido más general, no hay forma de comprobar si el iterador devuelto por iter es sensato aparte de probarlo.

  5. Si un objeto o implementos __iter__, el iter la función se asegurará que el objeto devuelto por __iter__ es un iterador No hay control de cordura si un objeto solo implementa __getitem__.

  6. __iter__ gana Si un objeto o implementa ambos __iter__ y __getitem__, iter(o) llamará __iter__.

  7. Si desea hacer sus propios objetos iterables, siempre implemente __iter__ método.

for bucles

Para poder seguir, necesita comprender lo que sucede cuando emplea un for loop en Python. Siéntase libre de pasar directamente a la siguiente sección si ya lo sabe.

Cuando usas for item in o para algún objeto iterable o, Llamadas Python iter(o) y espera un objeto iterador como el valor de retorno. Un iterador es cualquier objeto que implementa un __next__ (o next en Python 2) método y una __iter__ método.

Por convención, el __iter__ El método de un iterador debe devolver el objeto en sí (es decir return self) Python luego llama next en el iterador hasta StopIteration es elevado. 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

Iteracion 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 el punto 1 y 2: obtener un iterador y cheques poco confiables

Considera la siguiente clase:

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

Vocación iter con una instancia de BasicIterable devolverá un iterador sin ningún problema porque BasicIterable implementos __getitem__.

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

Sin embargo, es importante tener en cuenta que b no tiene el __iter__ atributo y no se considera una instancia de Iterable o Sequence:

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

Esta es la razón por Fluent Python por Luciano Ramalho recomienda llamar iter y manejando el potencial TypeError como 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 x es iterable es llamar iter(x) y manejar un TypeError excepción si no lo es. Esto es más preciso que usar isinstance(x, abc.Iterable) , porque iter(x) también considera el legado __getitem__ método, mientras que el Iterable ABC no.

En el punto 3: iteración sobre objetos que solo proporcionan __getitem__, pero no __iter__

Iterando sobre una instancia de BasicIterable funciona como se esperaba: Python construye un iterador que intenta buscar elementos por índice, comenzando en cero, hasta que IndexError es elevado. El objeto de la demostración __getitem__ método simplemente devuelve el item que se suministró como argumento para __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 aumenta StopIteration cuando no puede devolver el siguiente artículo y que el IndexError que se plantea para item == 3 se maneja internamente Esta es la razón por la que se repite un BasicIterable con un for loop 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ó iterintenta acceder a los elementos por índice. WrappedDict no hereda de dict, lo que significa que las instancias no tendrán __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__ son delegados a dict.__getitem__ para lo cual 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 el punto 4 y 5: iter busca un iterador cuando llama __iter__:

Cuando iter(o) se llama por un objeto o, iter se asegurará de que el valor de retorno de __iter__, si el método está presente, es un iterador. Esto significa que el objeto devuelto debe implementar __next__ (o next en Python 2) y __iter__. iter no puede realizar ningún control de cordura para objetos que solo proporcionar __getitem__, porque no tiene forma de comprobar 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 de FailIterIterable instancias falla inmediatamente, al construir un iterador de FailGetItemIterable tiene éxito, pero lanzará 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__, iter llamará __iter__. Considera la siguiente clase

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

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

y la salida cuando se repite una instancia:

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

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

Puede que se pregunte por qué la mayoría de las secuencias integradas list implementar un __iter__ método cuando __getitem__ sería suficiente

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, iteración sobre instancias de la clase anterior, que delega llamadas a __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

Las razones por las cuales sus iterables personalizados deberían implementar __iter__ son como sigue:

  1. Si implementa __iter__, las instancias se considerarán como iterables, y isinstance(o, collections.Iterable) regresará True.
  2. Si el objeto devuelto por __iter__ no es un iterador, iter fallará inmediatamente y levantará un 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: todos implementan __getitem__ . De hecho,   las secuencias estándar también implementan __iter__, y el tuyo también debería, porque el   manejo especial de __getitem__ existe por razones de compatibilidad hacia atrás y puede ser   ido en el futuro (aunque no está en desuso cuando escribo esto).


55
2018-04-04 16:04



Esto no es suficiente: el objeto devuelto por __iter__ debe implementar el protocolo de iteración (es decir next método). Ver la sección relevante en el documentación.

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


28
2017-12-23 12:17



try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

No ejecute cheques para ver si tu pato es realmente un pato para ver si es iterable o no, trátela como si fuera y que se queje si no fuera así.


18
2017-12-23 12:21



La mejor solución que he encontrado hasta ahora:

hasattr(obj, '__contains__')

que básicamente verifica si el objeto implementa el in operador.

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

  • es una expresión (funciona como lambda, a diferencia de intenta ... excepto variante)
  • es (debe ser) implementado por todos los iterables, incluyendo instrumentos de cuerda (Opuesto a __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 según su tipo (tratando los iterables en try y los no iterables en excepto haría trabajo, pero se vería muy feo y engañoso)
  • las soluciones a este problema que intentan realmente iterar sobre el objeto (por ejemplo, [x para x en obj]) para comprobar si es iterable pueden inducir penalizaciones de rendimiento significativas para los iterables grandes (especialmente si solo necesita los primeros elementos del iterable, para ejemplo) y debe evitarse

16
2017-11-09 16:40



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 integrados 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 puedes ver, tú poder registrar un objeto no iterable como Iterable, y generará una excepción en el tiempo de ejecución. Por lo tanto, isinstance adquiere un significado "nuevo", solo comprueba la compatibilidad de tipo "declarado", que es una buena forma de hacerlo en Python.

Por otro lado, si su objeto no satisface la interfaz que necesita, ¿qué va a hacer? Toma 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 esperaba, simplemente tira un TypeError, pero si se ha registrado el ABC apropiado, su cheque no será útil. 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ídese. Por otro lado, si necesita hacer cosas diferentes según el tipo de entrada, puede encontrar la infraestructura ABC bastante útil.


14
2017-12-23 13:35



Encontré una buena solución aquí:

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

11
2017-12-23 13:13



Puedes intentar esto:

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

Si podemos hacer un generador que itere sobre él (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?


10
2017-12-23 12:21