title in python plot
En Python, ¿cómo puedo determinar si un objeto es iterable? (19)
Escribiendo pato
try:
iterator = iter(theElement)
except TypeError:
# not iterable
else:
# iterable
# for obj in iterator:
# pass
Verificación de tipos
Usa las clases base abstractas . Necesitan al menos Python 2.6 y funcionan solo para clases de nuevo estilo.
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 lo describe la documentación :
Al verificar
isinstance(obj, Iterable)detecta las clases que están registradas como Iterable o que tienen un__iter__(), pero no detecta las clases que se repiten con el método__getitem__(). La única forma confiable de determinar si un objeto es iterable es llamar aiter(obj).
¿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.
La comprobación de
__iter__funciona en tipos de secuencia, pero fallaría en, por ejemplo, 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):try: some_object_iterator = iter(some_object) except TypeError as te: print some_object, ''is not iterable''El
iterincorporado comprueba el método__iter__o, en el caso de cadenas, el método__getitem__.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 Pythonic que determina el tipo de un objeto mediante la inspección de su método o firma de atributo en lugar de hacerlo mediante una relación explícita con algún tipo de objeto ("Si se parece a un pato y a los curanderos como a un pato , debe ser un pato ".) Enfatizando las interfaces 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 que usan type () o isinstance (). En su lugar, generalmente emplea el estilo de programación EAFP (Más fácil de perdonar que el permiso).
...
try: _ = (e for e in my_object) except TypeError: print my_object, ''is not iterable''El módulo de
collectionsproporciona algunas clases básicas abstractas, que permiten preguntar clases o instancias si proporcionan una funcionalidad particular, por ejemplo:import collections if isinstance(e, collections.Iterable): # e is iterableSin embargo, esto no comprueba las clases que son iterables a través de
__getitem__.
A menudo me parece conveniente, dentro de mis scripts, definir una función iterable . (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 lo harías con la función callable .
EDITAR: si tiene instalado Numpy, puede hacerlo simplemente: desde numpy import iterable , que es simplemente algo como
def iterable(obj):
try: iter(obj)
except: return False
return True
Si no tiene un número, simplemente puede implementar este código, o el anterior.
Aparte del intento regular y, excepto, podrías ejecutar la ayuda.
temp= [1,2,3,4]
help(temp)
La ayuda brindaría todos los métodos que podrían ejecutarse en ese objeto (podría ser cualquier objeto y no ser una lista como en el ejemplo), que es temporal en este caso.
Nota: Esto sería algo que harías manualmente.
De acuerdo con el Glosario de Python 2 , los iterables son
todos los tipos de secuencia (como
list,strytuple) y algunos tipos que no son de secuencia comodictyfiley objetos de cualquier clase que defina con un__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.
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__") . __iter__ verificar ambos, porque las __iter__ no tienen un método __iter__ (al menos no en Python 2, en Python 3) y porque los objetos generator no tienen un método __getitem__ .
Desde Python 3.5 puede usar el módulo de typing de la biblioteca estándar para cosas relacionadas con el tipo:
from typing import Iterable
...
if isinstance(my_item, Iterable):
print(True)
En Python <= 2.5, no puedes y no debes, 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 ABCs 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 el tiempo de ejecución. Por lo tanto, isinstance adquiere un significado "nuevo": solo comprueba la compatibilidad de tipo "declarada", que es una buena forma de utilizar Python.
Por otro lado, si su objeto no satisface la interfaz que necesita, ¿qué va a hacer? Tomemos 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 usted espera, simplemente lanza un TypeError, pero si se ha registrado el ABC correcto, su cheque es inútil. Por el contrario, si el método __iter__ está disponible, Python reconocerá automáticamente que el objeto de esa clase es iterable.
Entonces, si solo esperas un iterable, itéralo y olvídalo. Por otro lado, si necesita hacer cosas diferentes según el tipo de entrada, puede encontrar la infraestructura ABC bastante útil.
En lugar de verificar el atributo __iter__ , puede verificar el atributo __len__ , que es implementado por cada iterable Python integrado, 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 detecta los iterables definidos por el usuario que no lo implementan, ni tampoco las expresiones generadoras, con las que iter puede lidiar. Sin embargo, esto se puede hacer en una línea, y la adición de una comprobación simple or expresión para los generadores solucionaría este problema. (Tenga en cuenta que el type(my_generator_expression) == generator escritura type(my_generator_expression) == generator generaría un NameError . Consulte this respuesta en su lugar).
Puedes usar GeneratorType desde tipos:
>>> import types >>> types.GeneratorType <class ''generator''> >>> gen = (i for i in range(10)) >>> isinstance(gen, types.GeneratorType) True--- respuesta aceptada por utdemir
(Esto lo hace útil para verificar si puedes llamar a len en el objeto).
Esto no es suficiente: el objeto devuelto por __iter__ debe implementar el protocolo de iteración (es decir, el next método). Consulte la sección correspondiente en la documentation .
En Python, una buena práctica es "probar y ver" en lugar de "verificar".
He encontrado una buena solución here :
isiterable = lambda obj: isinstance(obj, basestring) /
or getattr(obj, ''__iter__'', False)
La forma más fácil, respetando la tipificación 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 :
- Es irrelevante la distinción entre si el objeto no es iterable o
__iter__se implementó un buggy__iter__, si el tipo de excepción es el mismo: de todos modos, no podrá iterar el objeto. Creo que entiendo su preocupación: ¿Cómo se puede
callablesi puedo confiar en la tipificación de pato para generar un__call__AttributeErrorsi__call__no está definido para mi objeto, pero ese no es el caso para una verificación iterable?No sé la respuesta, pero puede implementar la función que di (y otros usuarios), 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 la Creación del iterador del resto del código para que pueda capturar la excepción y distinguirla de otro
TypeError.
La función isiterable en el siguiente código devuelve True si el objeto es iterable. si no es iterable devuelve False
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
La mejor solución que he encontrado hasta ahora:
hasattr(obj, ''__contains__'')
que básicamente comprueba si el objeto implementa el operador in .
Ventajas (ninguna de las otras soluciones tiene las tres):
- es una expresión (funciona como un lambda , a diferencia del intento ... excepto la variante)
- está (debería) implementarse en todos los iterables, incluidas las cadenas (a diferencia de
__iter__) - Funciona en cualquier Python> = 2.5
Notas:
- la filosofía de Python de "pedir perdón, no permiso" no funciona bien, por ejemplo, en una lista tiene iterables y no iterables y necesita tratar cada elemento de manera diferente según su tipo (trata los iterables en try y non). iterables en excepto funcionaría, pero se vería feo y engañoso)
- las soluciones a este problema que intentan iterar realmente sobre el objeto (por ejemplo, [x para x en obj]) para comprobar si es iterable pueden generar importantes penalizaciones en el rendimiento para iterables grandes (especialmente si solo necesita los primeros elementos de iterable, por ejemplo) ejemplo) y debe evitarse
Me gustaría __iter__ un poco más la interacción de iter , __iter__ y __getitem__ y lo que sucede detrás de las cortinas. 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 recordatorio rápido de lo que sucede cuando emplea un bucle for en python, seguido de una discusión para ilustrar los hechos.
Hechos
Puede obtener un iterador de cualquier objeto
ollamando aiter(o)si al menos una de las siguientes condiciones se cumple:
a)otiene un método__iter__que devuelve un objeto iterador. Un iterador es cualquier objeto con un__iter__y__next__(Python 2:next).
b)otiene un método__getitem__Verificar una instancia de
IterableoSequence, o verificar el atributo__iter__no es suficiente.Si un objeto
oimplementa solo__getitem__, pero no__iter__,iter(o)construirá un iterador que intenta obtener elementos deopor índice entero, comenzando en el índice 0. El iterador detectará cualquierIndexError(pero no otros errores) que seaStopIterationy luego planteaStopIterationsí.En el sentido más general, no hay forma de comprobar si el iterador devuelto por
iteres más que de prueba.Si un objeto
oimplementa__iter__, la funcióniterse asegurará de que el objeto devuelto por__iter__sea un iterador. No hay comprobación de validez si un objeto solo implementa__getitem__.__iter__gana. Si un objetooimplementa tanto__iter__como__getitem__,iter(o)llamará__iter__.Si desea que sus propios objetos sean iterables, siempre implemente el método
__iter__.
for bucles
Para seguir adelante, necesita comprender lo que sucede cuando emplea un bucle for en Python. Siéntase libre de pasar directamente a la siguiente sección si ya lo sabe.
Cuando se usa for item in o para algún objeto iterable, Python llama iter(o) y espera un objeto iterador como valor de retorno. Un iterador es cualquier objeto que implementa un __next__ (o next en Python 2) y un método __iter__ .
Por convención, el método __iter__ de un iterador debe devolver el objeto en sí (es decir, return self ). Python luego llama al next iterador hasta que se StopIteration . 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 cheques no confiables
Considere la siguiente clase:
class BasicIterable(object):
def __getitem__(self, item):
if item == 3:
raise IndexError
return item
Llamar a iter con una instancia de BasicIterable devolverá un iterador sin ningún problema porque BasicIterable implementa __getitem__ .
>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>
Sin embargo, es importante tener en cuenta que b no tiene el atributo __iter__ 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 la cual Fluent Python de Luciano Ramalho recomienda llamar a iter y manejar el posible 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
xes iterable es llamar aiter(x)y manejar una excepciónTypeErrorsi no lo es. Esto es más preciso que usarisinstance(x, abc.Iterable), porqueiter(x)también considera el método heredado__getitem__, mientras queIterableABC no lo hace.
En el punto 3: Iterar sobre objetos que solo proporcionan __getitem__ , pero no __iter__
La iteración sobre una instancia de BasicIterable funciona como se esperaba: Python construye un iterador que intenta obtener elementos por índice, comenzando desde cero, hasta que se IndexError un IndexError . El método __getitem__ del objeto de demostración simplemente devuelve el item que fue suministrado como argumento a __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 genera StopIteration cuando no puede devolver el siguiente elemento y que el IndexError que se IndexError para el item == 3 se maneja internamente. Esta es la razón por la que el bucle sobre un BasicIterable con un bucle for funciona como se espera:
>>> for x in b:
... print(x)
...
0
1
2
Aquí hay otro ejemplo para llevar a casa el concepto de cómo el iterador devuelto por iter intenta acceder a los elementos por índice. WrappedDict no hereda de dict , lo que significa que las instancias no tendrán un método __iter__ .
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 a dict.__getitem__ para las cuales la notación de corchete es simplemente una forma abreviada.
>>> w = WrappedDict({-1: ''not printed'',
... 0: ''hi'', 1: '''', 2: ''!'',
... 4: ''not printed'',
... ''x'': ''not printed''})
>>> for x in w:
... print(x)
...
hi
!
En los puntos 4 y 5: iter busca un iterador cuando llama __iter__ :
Cuando se llama a iter(o) para un objeto o , iter se 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 next en Python 2) y __iter__ . iter no puede realizar comprobaciones de integridad para los objetos que solo proporcionan __getitem__ , porque no tiene forma de verificar si los elementos del objeto son accesibles por el í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 instancias FailIterIterable falla inmediatamente, mientras que la construcción de un iterador a partir de FailGetItemIterable tiene éxito, 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__ , 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 realiza un bucle en una instancia:
>>> iwd = IterWinsDemo()
>>> for x in iwd:
... print(x)
...
__iter__
wins
En el punto 7: sus clases iterables deberían implementar __iter__
Podría preguntarse por qué la mayoría de las secuencias __iter__ como list implementan un método __iter__ 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, la iteración sobre las instancias de la clase anterior, que delega las llamadas a __getitem__ para list.__getitem__ (usando la notación entre corchetes), funcionará bien:
>>> wl = WrappedList([''A'', ''B'', ''C''])
>>> for x in wl:
... print(x)
...
A
B
C
Las razones por las que sus iterables personalizados deben implementar __iter__ son las siguientes:
- Si implementa
__iter__, las instancias se considerarán como iterables y laisinstance(o, collections.Iterable)devolveráTrue. - Si el objeto devuelto por
__iter__no es un iterador,iterfallará inmediatamente y generará unTypeError. - El manejo especial de
__getitem__existe por razones de compatibilidad hacia atrás. Citando de nuevo desde 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 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 cuando escribo esto).
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 se repita sobre él (pero nunca lo use para que no ocupe espacio), es iterable. Parece una especie de "duh". ¿Por qué necesitas determinar si una variable es iterable en primer lugar?
Siempre me ha eludido por qué python tiene que ser callable(obj) -> bool pero no iterable(obj) -> bool ...
seguramente es más fácil hacer hasattr(obj,''__call__'') incluso si es más lento.
Ya que casi todas las demás respuestas recomiendan el uso de try / except TypeError , donde la prueba de excepciones generalmente se considera una mala práctica en cualquier idioma, aquí hay una implementación de iterable(obj) -> bool me ha gustado más y que uso con frecuencia:
Para Python 2, usaré un lambda solo para aumentar el rendimiento ...
(en Python 3 no importa lo que uses para definir la función, def tiene 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 con __iter__ ya que no prueba para __getitem__ .
La mayoría de los objetos iterables deben depender de __iter__ donde los objetos de casos especiales __iter__ a __getitem__ , aunque cualquiera de los dos es necesario para que un objeto sea iterable.
(y dado que esto es estándar, también afecta a los objetos C)
pandas tiene una función incorporada como esa:
from pandas.util.testing import isiterable
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 llevar 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 cuerdas. Ú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 cadenas de bytes de tipo y bytearray .
-
bytesobjetos debytesen Python 3 son iterables.True == is_iterable(b"string") == is_iterable("string".encode(''utf-8''))No hay tal tipo en Python 2. -
bytearrayobjetosbytearrayen Python 2 y 3 son iterablesTrue == is_iterable(bytearray(b"abc"))
El hasattr(x, ''__iter__'') OP hasattr(x, ''__iter__'') dirá sí a las cadenas en Python 3 y no en Python 2 (no importa si '''' o b'''' o u'''' ). Gracias a @LuisMasuelli por darse cuenta de que también te __iter__ un buggy __iter__ .
try:
#treat object as iterable
except TypeError, e:
#object is not actually iterable
No ejecute los controles para ver si su pato es realmente un pato para ver si es iterable o no, trátelo como si fuera y reclame si no lo fue.