No Python, como determino se um objeto é iterável?

1084

Existe um método como isiterable? A única solução que encontrei até agora é chamar

hasattr(myObj, '__iter__')

Mas não tenho certeza de como isso é à prova de idiotas.

willem
fonte
18
__getitem__Também é suficiente para fazer um objeto iterável
Kos
4
FWIW: será iter(myObj)bem - sucedido se isinstance(myObj, dict), portanto, se você estiver olhando para uma myObjque possa ser uma sequência de dicts ou uma única dict, terá êxito nos dois casos. Uma sutileza que é importante se você quiser saber o que é uma sequência e o que não é. (em Python 2)
Ben Mosher
7
__getitem__também é suficiente para tornar um objeto iterável ... se começar no índice zero .
Carlos A. Gómez

Respostas:

27

Ultimamente tenho estudado esse problema. Com base nisso, minha conclusão é que hoje em dia essa é a melhor abordagem:

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

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

O exposto acima já foi recomendado anteriormente, mas o consenso geral é de que o uso iter()seria melhor:

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

Também usamos iter()nosso código para esse fim, mas ultimamente comecei a ficar cada vez mais irritado com objetos que apenas __getitem__são considerados iteráveis. Há razões válidas para ter __getitem__em um objeto não iterável e, com elas, o código acima não funciona bem. Como exemplo da vida real, podemos usar o Faker . O código acima relata que é iterável, mas realmente tentar iterá-lo causa um AttributeError(testado com o 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'

Se usássemos insinstance(), não consideraríamos acidentalmente instâncias do Faker (ou quaisquer outros objetos que tenham apenas __getitem__):

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

As respostas anteriores comentaram que o uso iter()é mais seguro, pois o antigo modo de implementar a iteração no Python era baseado __getitem__e a isinstance()abordagem não detectaria isso. Isso pode ter sido verdade nas versões antigas do Python, mas com base nos meus testes exaustivos isinstance()funciona muito bem hoje em dia. O único caso em isinstance()que não funcionou, mas iter()funcionou, UserDictao usar o Python 2. Se isso for relevante, é possível usá isinstance(item, (Iterable, UserDict))-lo para obter isso.

Pekka Klärck
fonte
1
Também typing.Dicté considerado iterável por, iter(Dict)mas list(Dict)falha com erro TypeError: Parameters to generic types must be types. Got 0.. Como esperado, isinstance(Dict, Iterable)retorna falso.
Pekka Klärck 16/04
1
Cheguei à mesma conclusão, mas por razões diferentes. O uso iterfez com que alguns dos nossos códigos que usavam "pré-cache" desacelerassem desnecessariamente. Se o __iter__código for lento, será o mesmo que chamar iter... sempre que você quiser ver se algo é iterável.
thorwhalen
842
  1. A verificação de __iter__trabalhos nos tipos de sequência, mas falharia em, por exemplo, seqüências de caracteres no Python 2 . Também gostaria de saber a resposta certa, até então, aqui está uma possibilidade (que funcionaria também em strings):

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

    A iterverificação interna do __iter__método ou, no caso de seqüências de caracteres, o __getitem__método.

  2. Outra abordagem pitônica geral é assumir uma iterável e falhar normalmente se não funcionar no objeto especificado. O glossário Python:

    Estilo de programação Python que determina o tipo de um objeto pela inspeção de seu método ou assinatura de atributo, e não por um relacionamento explícito com algum objeto de tipo ("Se ele se parece com um pato e grasna como um pato , deve ser um pato .") Enfatizando as interfaces em vez de tipos específicos, o código bem projetado aprimora sua flexibilidade, permitindo a substituição polimórfica. A digitação de pato evita testes usando type () ou isinstance (). Em vez disso, normalmente emprega o estilo de programação EAFP (mais fácil pedir perdão do que permissão).

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
    
  3. O collectionsmódulo fornece algumas classes base abstratas, que permitem perguntar a classes ou instâncias se elas fornecem funcionalidade específica, por exemplo:

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

    No entanto, isso não verifica as classes que são iteráveis __getitem__.

miku
fonte
34
[e for e in my_object]pode gerar uma exceção por outros motivos, ou seja, my_objecté indefinido ou possível erro na my_objectimplementação.
Nick Dandoulakis
37
Uma string é uma sequência ( isinstance('', Sequence) == True) e, como qualquer sequência, é iterável ( isinstance('', Iterable)). Embora hasattr('', '__iter__') == Falsee possa ser confuso.
JFS
82
Se my_objectfor muito grande (digamos, infinito itertools.count()), a compreensão da sua lista consumirá muito tempo / memória. Melhor criar um gerador, que nunca tentará criar uma lista (potencialmente infinita).
31711 Chris Lutz
14
E se some_object lançar TypeError causado por outro motivo (bugs etc.) também? Como podemos diferenciá-lo do "Não é iterável TypeError"?
Shaung 13/09/11
54
Observe que no Python 3: hasattr(u"hello", '__iter__')retornaTrue
Carlos
572

Digitação de pato

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

# for obj in iterator:
#     pass

Verificação de tipo

Use as classes base abstratas . Eles precisam pelo menos do Python 2.6 e funcionam apenas para classes de novo estilo.

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

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

No entanto, iter()é um pouco mais confiável, conforme descrito na documentação :

A verificação isinstance(obj, Iterable)detecta classes que são registradas como Iterable ou que possuem um __iter__()método, mas não detecta classes que iteram com o __getitem__() método. A única maneira confiável de determinar se um objeto é iterável é chamar iter(obj).

Georg Schölly
fonte
18
De "Fluent Python", de Luciano Ramalho: A partir do Python 3.4, a maneira mais precisa de verificar se um objeto x é iterável é chamar iter (x) e manipular uma exceção TypeError, se não for. Isso é mais preciso do que usar isinstance (x, abc.Iterable), porque o iter (x) também considera o método getitem herdado , enquanto o Iterable ABC não.
RdB
Caso você esteja pensando "ah, vou apenas em isinstance(x, (collections.Iterable, collections.Sequence))vez de iter(x)", observe que isso ainda não detectará um objeto iterável que implemente apenas, __getitem__mas não __len__. Use iter(x)e pegue a exceção.
Dale
Sua segunda resposta não funciona. Em PyUNO, se eu fizer iter(slide1), tudo vai bem, no entanto, os isinstance(slide1, Iterable)lances TypeError: issubclass() arg 1 must be a class.
Hi-Angel
@ Hi-Angel soa como um bug no PyUNOObserve que sua mensagem de erro diz em issubclass()vez de isinstance().
Georg Schölly 23/07/19
2
Chamar iter () sobre um objeto pode ser uma operação cara (consulte DataLoader no Pytorch, que bifurca / gera vários processos no iter ()).
szali
126

Eu gostaria de esclarecer um pouco mais de luz sobre a interação de iter, __iter__e __getitem__eo que acontece por trás das cortinas. Armado com esse conhecimento, você será capaz de entender por que o melhor que você pode fazer é

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

Vou listar os fatos primeiro e depois acompanhar com um lembrete rápido do que acontece quando você emprega um forloop em python, seguido de uma discussão para ilustrar os fatos.

Fatos

  1. Você pode obter um iterador de qualquer objeto ochamando iter(o)se pelo menos uma das seguintes condições for verdadeira:

    a) opossui um __iter__método que retorna um objeto iterador. Um iterador é qualquer objeto com um método __iter__e __next__(Python 2 next:).

    b) otem um __getitem__método

  2. Verificar uma instância de Iterableou Sequenceou verificar o atributo __iter__não é suficiente.

  3. Se um objeto oimplementar apenas __getitem__, mas não __iter__, iter(o)construirá um iterador que tenta buscar itens opelo índice inteiro, iniciando no índice 0. O iterador capturará qualquer IndexError(mas nenhum outro erro) gerado e, em seguida, se elevará StopIteration.

  4. No sentido mais geral, não há como verificar se o iterador retornado iteré sensato, exceto para testá-lo.

  5. Se um objeto for oimplementado __iter__, a iterfunção garantirá que o objeto retornado por __iter__seja um iterador. Não há verificação de integridade se um objeto é implementado apenas __getitem__.

  6. __iter__vitórias. Se um objeto oimplementa ambos __iter__e __getitem__, iter(o)será chamado __iter__.

  7. Se você deseja tornar seus próprios objetos iteráveis, sempre implemente o __iter__método

for rotações

Para acompanhar, você precisa entender o que acontece quando você emprega um forloop no Python. Sinta-se livre para pular para a próxima seção, se você já sabe.

Quando você usa for item in opara algum objeto iterável o, o Python chama iter(o)e espera um objeto iterador como o valor de retorno. Um iterador é qualquer objeto que implementa um método __next__(ou nextno Python 2) e um __iter__método.

Por convenção, o __iter__método de um iterador deve retornar o próprio objeto (ou seja return self). O Python então chama nexto iterador até que StopIterationseja gerado. Tudo isso acontece implicitamente, mas a seguinte demonstração o torna visível:

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

Iteração sobre um 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

Discussão e ilustrações

Nos pontos 1 e 2: obtendo um iterador e verificações não confiáveis

Considere a seguinte classe:

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

Chamar itercom uma instância de BasicIterableretornará um iterador sem problemas, porque BasicIterableimplementa __getitem__.

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

No entanto, é importante observar que bnão possui o __iter__atributo e não é considerado uma instância Iterableou Sequence:

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

É por isso que o Fluent Python de Luciano Ramalho recomenda chamar itere manipular o potencial TypeErrorcomo a maneira mais precisa de verificar se um objeto é iterável. Citando diretamente do livro:

No Python 3.4, a maneira mais precisa de verificar se um objeto xé iterável é chamar iter(x)e manipular uma TypeErrorexceção, se não for. Isso é mais preciso do que usar isinstance(x, abc.Iterable), porque iter(x)também considera o __getitem__método legado , enquanto o IterableABC não.

No ponto 3: Iterando sobre objetos que fornecem apenas __getitem__, mas não__iter__

Iterando sobre uma instância de BasicIterableobras conforme o esperado: Python constrói um iterador que tenta buscar itens por índice, começando em zero, até que um IndexErrorseja gerado. O __getitem__método do objeto demo simplesmente retorna o itemque foi fornecido como argumento __getitem__(self, item)pelo iterador retornado 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

Observe que o iterador dispara StopIterationquando não pode retornar o próximo item e que o IndexErrorque é levantado item == 3é tratado internamente. É por isso que repetir BasicIterableum forloop com um loop funciona conforme o esperado:

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

Aqui está outro exemplo para esclarecer o conceito de como o iterador retornou itertentando acessar itens por índice. WrappedDictnão herda dict, o que significa que as instâncias não terão um __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

Observe que as chamadas para __getitem__são delegadas dict.__getitem__para as quais a notação de colchete é simplesmente uma abreviação.

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

Nos pontos 4 e 5: iterverifica um iterador quando ele chama__iter__ :

Quando iter(o)é chamado para um objeto o, itergarante que o valor de retorno de __iter__, se o método estiver presente, seja um iterador. Isso significa que o objeto retornado deve implementar __next__(ou nextno Python 2) e __iter__. iternão pode executar nenhuma verificação de integridade para objetos que apenas fornecem __getitem__, porque não há como verificar se os itens do objeto estão acessíveis pelo índice inteiro.

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

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

Observe que a construção de um iterador a partir de FailIterIterableinstâncias falha imediatamente, enquanto a construção de um iterador FailGetItemIterableé bem - sucedida, mas lança uma exceção na primeira chamada para __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

No ponto 6: __iter__vitórias

Este é direto. Se um objeto implementa __iter__e __getitem__, iterserá chamado __iter__. Considere a seguinte classe

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

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

e a saída ao fazer um loop sobre uma instância:

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

No ponto 7: suas classes iteráveis ​​devem implementar __iter__

Você pode se perguntar por que a maioria das seqüências internas, como listimplementar um __iter__método, __getitem__seria suficiente.

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

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

Afinal, iteração sobre as instâncias da classe acima, que delega chamadas para __getitem__a list.__getitem__(usando a notação colchete), vai funcionar bem:

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

Os motivos pelos quais os iterables personalizados devem implementar __iter__são os seguintes:

  1. Se você implementar __iter__, as instâncias serão consideradas iteráveis ​​e isinstance(o, collections.abc.Iterable)retornarão True.
  2. Se o objeto retornado por __iter__não for um iterador, iterfalhará imediatamente e aumentará a TypeError.
  3. O tratamento especial de __getitem__existe por motivos de compatibilidade com versões anteriores. Citando novamente o Fluent Python:

É por isso que qualquer sequência Python é iterável: todas elas são implementadas __getitem__. De fato, as seqüências padrão também são implementadas __iter__, e a sua também, porque o tratamento especial de __getitem__existe por razões de compatibilidade com versões anteriores e pode desaparecer no futuro (embora não seja preterido enquanto escrevo isso).

timgeb
fonte
portanto, é seguro definir um predicado is_iterableretornando Trueno trybloco e Falseno except TypeErrorbloco?
Alancalvitti
Esta é uma ótima resposta. Eu acho que destaca a natureza não intuitiva e infeliz do protocolo getitem. Nunca deveria ter sido adicionado.
Neil G
31

Isso não é suficiente: o objeto retornado por __iter__deve implementar o protocolo de iteração (ou seja, nextmétodo). Veja a seção relevante na documentação .

No Python, uma boa prática é "tentar ver" em vez de "verificar".

jldupont
fonte
9
"digitando pato" eu acredito? :)
Willem
9
@willem: ou "não pedir permissão, mas por perdão" ;-)
jldupont
14
@willem Os estilos "permissão" e "perdão" são qualificados como digitação de pato. Se você perguntar o que um objeto pode fazer e não o que é , isso significa digitação de pato. Se você usa introspecção, isso é "permissão"; se você apenas tentar fazê-lo e ver se funciona ou não, isso é "perdão".
Mark Reed
22

No Python <= 2.5, você não pode e não deveria - iterável era uma interface "informal".

Mas, desde o Python 2.6 e 3.0, você pode aproveitar a nova infraestrutura ABC (abstract base class) juntamente com alguns ABCs internos disponíveis no módulo de coleções:

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)

Agora, se isso é desejável ou realmente funciona, é apenas uma questão de convenções. Como você pode ver, você pode registrar um objeto não iterável como Iterable - e isso gerará uma exceção no tempo de execução. Portanto, isinstance adquire um significado "novo" - apenas verifica a compatibilidade do tipo "declarado", o que é um bom caminho a percorrer no Python.

Por outro lado, se seu objeto não satisfizer a interface que você precisa, o que você fará? Veja o seguinte exemplo:

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

Se o objeto não satisfizer o que você espera, basta lançar um TypeError, mas se o ABC adequado tiver sido registrado, sua verificação será inútil. Pelo contrário, se o __iter__método estiver disponível, o Python reconhecerá automaticamente o objeto dessa classe como iterável.

Portanto, se você apenas espera um iterável, itere sobre ele e esqueça. Por outro lado, se você precisar fazer coisas diferentes, dependendo do tipo de entrada, poderá achar a infraestrutura ABC bastante útil.

Alan Franzoni
fonte
13
não use bare except:no código de exemplo para iniciantes. Promove más práticas.
JFS
JFS: Eu não, mas precisava passar por vários códigos de exceção e não queria capturar a exceção específica ... Acho que o objetivo desse código é bastante claro.
27712 Alan Franzoni
21
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

Não faça verificações para ver se o seu pato é realmente um pato para ver se é iterável ou não, trate-o como se fosse e reclame se não foi.

badp
fonte
3
Tecnicamente, durante a iteração, seu cálculo pode dar um TypeErrore tirar você daqui, mas basicamente sim.
23711 Chris Lutz
6
@ William: Por favor, use timeit para realizar uma referência. As exceções do Python geralmente são mais rápidas que as instruções if. Eles podem seguir um caminho um pouco mais curto pelo intérprete.
23409 S.Lott
2
@ William: IronPython tem lentas (em comparação com CPython) exceções.
JFS
2
Uma tentativa de trabalho: a afirmação é realmente rápida. Portanto, se você tiver poucas exceções, tente exceto é rápido. Se você espera muitas exceções, "se" pode ser mais rápido.
Arne Babenhauserheide
2
O objeto de exceção não deve ser capturado adicionando " as e" depois em TypeErrorvez de adicionar " , e"?
hellogoodbye
21

Desde o Python 3.5, você pode usar o módulo de digitação da biblioteca padrão para coisas relacionadas ao tipo:

from typing import Iterable

...

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

A melhor solução que encontrei até agora:

hasattr(obj, '__contains__')

que basicamente verifica se o objeto implementa o inoperador.

Vantagens (nenhuma das outras soluções possui as três):

  • é uma expressão (funciona como um lambda , em oposição à tentativa ... exceto variante)
  • é (deve ser) implementado por todos os iterables, incluindo strings (ao contrário de __iter__)
  • funciona em qualquer Python> = 2.5

Notas:

  • a filosofia Python de "pedir perdão, não permissão" não funciona bem quando, por exemplo, em uma lista você tem iteráveis ​​e não iteráveis ​​e precisa tratar cada elemento de maneira diferente de acordo com seu tipo (tratando iteráveis ​​em try e non- iterables sobre exceto iria funcionar, mas ele ficaria butt-feio e enganosa)
  • soluções para esse problema que tentam realmente iterar sobre o objeto (por exemplo, [x para x em obj]) para verificar se é iterável podem induzir penalidades significativas de desempenho para iteráveis ​​grandes (especialmente se você só precisa dos primeiros elementos do iterável, por exemplo) e deve ser evitado
Vlad
fonte
3
Bom, mas por que não usar o módulo de coleções, conforme proposto em stackoverflow.com/questions/1952464/… ? Parece mais expressivo para mim.
Dave Abrahams
1
É mais curto (e não requer importações adicionais) sem perder a clareza: ter um método "contém" parece uma maneira natural de verificar se algo é uma coleção de objetos.
Vlad
46
Só porque algo pode conter algo não significa necessariamente que é iterável. Por exemplo, um usuário pode verificar se um ponto está em um cubo 3D, mas como você iteraria nesse objeto?
Casey Kuball
13
Isto está incorreto. Um iterável em si não suporta contém , pelo menos no Python 3.4.
quer
15

Você pode tentar o seguinte:

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

Se pudermos criar um gerador que itere sobre ele (mas nunca use o gerador para não ocupar espaço), é iterável. Parece um tipo de coisa "duh". Por que você precisa determinar se uma variável é iterável em primeiro lugar?

Chris Lutz
fonte
Que tal iterable(itertools.repeat(0))? :)
badp
5
@badp, o (x for x in a)just cria um gerador, ele não faz nenhuma iteração no a.
catchmeifyoutry 23/12/2009
5
Tentar é (x for x in a)precisamente equivalente a tentar iterator = iter(a)? Ou há alguns casos em que os dois são diferentes?
máximo
Não é for _ in a: breakmais direto? É mais lento?
23415 Mr_and_Mrs_D # 23/15
2
@Mr_and_Mrs_D é ruim se o objeto testado for um iterador que será iterado posteriormente (será 1 item curto, pois sua posição não pode ser redefinida), a criação de geradores de lixo não itera sobre o objeto, pois não é iterada, embora eu não tenha certeza de que isso aumentará 100% um TypeError se não for iterável.
Tcll
13

Encontrei uma boa solução aqui :

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

De acordo com o Glossário Python 2 , iteráveis ​​são

todos os tipos de sequências (tais como list, stre tuple) e alguns tipos de sequências não como dicte filee objectos de todas as classes definidas com um __iter__()ou __getitem__()método. Os iteráveis ​​podem ser usados ​​em um loop for e em muitos outros lugares em que uma sequência é necessária (zip (), mapa (), ...). Quando um objeto iterável é passado como argumento para a função interna iter (), ele retorna um iterador para o objeto.

Obviamente, dado o estilo geral de codificação do Python com base no fato de que é "mais fácil pedir perdão do que permissão"., A expectativa geral é usar

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

Mas se você precisar checá-lo explicitamente, poderá testar um iterável por hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). Você precisa verificar os dois, porque strs não possui um __iter__método (pelo menos não no Python 2, no Python 3) e porque os generatorobjetos não têm um __getitem__método.

Anaphory
fonte
8

Costumo achar conveniente, dentro dos meus scripts, definir uma iterablefunção. (Agora incorpora a simplificação sugerida por Alfe):

import collections

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

para que você possa testar se algum objeto é iterável da forma mais legível

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

como você faria com a callablefunção

EDIT: se você tiver o numpy instalado, você pode simplesmente fazer: from numpy import iterable, que é simplesmente algo como

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

Se você não tiver um numpy, pode simplesmente implementar esse código, ou o código acima.

fmonegaglia
fonte
3
Sempre que você gosta if x: return True else: return False( xsendo booleano), pode escrever isso como return x. No seu caso, return isinstance(…)sem qualquer if.
Alfe
Como você reconhece que a solução da Alfe é melhor, por que você não editou sua resposta para simplesmente dizer isso? Em vez disso, você agora tem duas versões em sua resposta. Verbosidade desnecessária. Enviando uma edição para corrigir isso.
Home
2
Você deve pegar "TypeError" na linha `exceto: return False`. Capturar tudo é um padrão ruim.
Mariusz Jamro 03/07/2014
Saiba disso. Traduzi esse trecho de código da biblioteca NumPy, que usa a exceção genérica.
Fmonegaglia
Só porque um código é retirado do NumPy não significa que seja bom ... padrão ou não, o único momento em que tudo deve ser feito é se você estiver explicitamente manipulando erros dentro do seu programa.
Tcll em 14/08
5

tem uma função interna assim:

from pandas.util.testing import isiterable
Sören
fonte
no entanto, isso apenas verifica se existe __iter__e realmente não se importa com sequências e similares.
ead 3/03
4

Sempre me iludiu por que o python tem, callable(obj) -> boolmas não iterable(obj) -> bool...
certamente é mais fácil de fazer, hasattr(obj,'__call__')mesmo que seja mais lento.

Como quase todas as outras respostas recomendam o uso de try/ except TypeError, onde o teste de exceções geralmente é considerado uma prática inadequada em qualquer idioma, aqui está uma implementação da qual iterable(obj) -> booleu mais gosto e uso com frequência:

Pelo bem do python 2, usarei um lambda apenas para esse aumento extra de desempenho ...
(no python 3, não importa o que você usa para definir a função, deftem aproximadamente a mesma velocidade que lambda)

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

Observe que essa função é executada mais rapidamente para objetos com __iter__uma vez que não é testada __getitem__.

A maioria dos objetos iteráveis ​​deve depender de __iter__onde os objetos de caso especial se enquadram __getitem__, embora ambos sejam necessários para que um objeto seja iterável.
(e, como isso é padrão, também afeta objetos C)

Tcll
fonte
ele não fornece código de trabalho, muito menos fala sobre desempenho de python ... embora essa resposta tenha sido realmente apenas por conveniência, como eu já vi várias vezes aqui.
Tcll
3
def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

Isso diz sim a todos os tipos de objetos iteráveis, mas diz não a seqüências de caracteres no Python 2 . (É o que eu quero, por exemplo, quando uma função recursiva pode pegar uma string ou um contêiner de strings. Nessa situação, pedir perdão pode levar ao obscurecimento, e é melhor pedir permissão primeiro.)

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)

Muitas outras estratégias aqui dirão sim às seqüências de caracteres. Use-os se é isso que você deseja.

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á sim para strings do tipo bytese bytearray.

  • bytes objetos no Python 3 são iteráveis True == is_iterable(b"string") == is_iterable("string".encode('utf-8')) Não existe esse tipo no Python 2.
  • bytearray objetos em Python 2 e 3 são iteráveis True == is_iterable(bytearray(b"abc"))

O OP hasattr(x, '__iter__')abordagem vai dizer sim para strings em Python 3 e não em Python 2 (não importa se ''ou b''ou u''). Graças a @LuisMasuelli por perceber, também o decepcionará __iter__.

Bob Stein
fonte
2

A maneira mais fácil, respeitando a digitação do pato do Python , é capturar o erro (o Python sabe perfeitamente o que ele espera de um objeto para se tornar um 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. É irrelevante a distinção entre o objeto não ser iterável ou um buggy __iter__ foi implementado, se o tipo de exceção for o mesmo: de qualquer maneira, você não poderá iterar o objeto.
  2. Acho que entendo sua preocupação: como callableexiste uma verificação se eu também poderia confiar na digitação do pato para gerar uma AttributeErrorif __call__não definida para o meu objeto, mas esse não é o caso da verificação iterável?

    Não sei a resposta, mas você pode implementar a função que eu (e outros usuários) fornecemos ou apenas capturar a exceção em seu código (sua implementação nessa parte será como a função que escrevi - apenas garanta que você isole o criação do iterador a partir do restante do código, para que você possa capturar a exceção e diferenciá-la de outra TypeError.

Luis Masuelli
fonte
1

A isiterablefunção no código a seguir retorna Truese o objeto for iterável. se não for retornos iteráveisFalse

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

exemplo

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ômade
fonte
2
tantas respostas detalhadas acima com muitos upvotes e você joga em uma resposta inexplicável ... meh
Nrzonline
Por favor, não poste código nu. Inclua também uma explicação do que isso está fazendo.
Jonathan Mee
1

Em vez de verificar o __iter__atributo, você pode verificar o __len__atributo, que é implementado por cada python integrado no iterável, incluindo strings.

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

Objetos não iteráveis ​​não implementariam isso por razões óbvias. No entanto, ele não captura iteráveis ​​definidos pelo usuário que não o implementam, nem expressões geradoras com as quais iterpossa lidar. No entanto, isso pode ser feito em uma linha, e a adição de uma orverificação de expressão simples para geradores resolveria esse problema. (Observe que a escrita type(my_generator_expression) == generatorgeraria a NameError. Consulte esta resposta.)

Você pode usar o GeneratorType dos tipos:

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

--- resposta aceita por utdemir

(Isso o torna útil para verificar se você pode chamar leno objeto.)

DarthCadeus
fonte
infelizmente, nem todos os objetos iteráveis ​​usam __len__... nesse caso, geralmente é o uso inadequado do cálculo da distância entre dois objetos. onde obj.dist()poderia ser facilmente substituído.
Tcll
Sim. A maioria dos iterables definidos pelo usuário implementa iter e getitem, mas não o len. No entanto, os tipos internos fazem isso e, se você quiser verificar se pode chamar a função len, a verificação de len é mais segura. Mas você está certo.
DarthCadeus
0

Não é realmente "correto", mas pode servir como verificação rápida dos tipos mais comuns, como strings, tuplas, flutuadores, 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
fonte
0

Meio atrasado para a festa, mas eu me fiz essa pergunta e vi isso, depois pensei em uma resposta. Não sei se alguém já postou isso. Mas, basicamente, notei que todos os tipos iteráveis têm __getitem __ () em seu ditado. É assim que você verifica se um objeto é iterável sem nem tentar. (Chalaça pretendida)

def is_attr(arg):
    return '__getitem__' in dir(arg)
lakam99
fonte
Infelizmente, isso não é confiável. Exemplo
timgeb 26/01
1
Objetos definidos são outro contra-exemplo.
Raymond Hettinger 13/04