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
iter
incorporado 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
collections
proporciona 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 iterable
Sin 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
,str
ytuple
) y algunos tipos que no son de secuencia comodict
yfile
y 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
callable
si puedo confiar en la tipificación de pato para generar un__call__
AttributeError
si__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
o
llamando aiter(o)
si al menos una de las siguientes condiciones se cumple:
a)o
tiene un método__iter__
que devuelve un objeto iterador. Un iterador es cualquier objeto con un__iter__
y__next__
(Python 2:next
).
b)o
tiene un método__getitem__
Verificar una instancia de
Iterable
oSequence
, o verificar el atributo__iter__
no es suficiente.Si un objeto
o
implementa solo__getitem__
, pero no__iter__
,iter(o)
construirá un iterador que intenta obtener elementos deo
por índice entero, comenzando en el índice 0. El iterador detectará cualquierIndexError
(pero no otros errores) que seaStopIteration
y luego planteaStopIteration
sí.En el sentido más general, no hay forma de comprobar si el iterador devuelto por
iter
es más que de prueba.Si un objeto
o
implementa__iter__
, la funcióniter
se 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 objetoo
implementa 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
x
es iterable es llamar aiter(x)
y manejar una excepciónTypeError
si no lo es. Esto es más preciso que usarisinstance(x, abc.Iterable)
, porqueiter(x)
también considera el método heredado__getitem__
, mientras queIterable
ABC 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,iter
fallará 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
.
-
bytes
objetos debytes
en Python 3 son iterables.True == is_iterable(b"string") == is_iterable("string".encode(''utf-8''))
No hay tal tipo en Python 2. -
bytearray
objetosbytearray
en 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.