In Python, come posso determinare se un oggetto è iterabile?


1085

C'è un metodo simile isiterable? L'unica soluzione che ho trovato finora è quella di chiamare

hasattr(myObj, '__iter__')

Ma non sono sicuro di quanto sia infallibile.


18
__getitem__è anche sufficiente per rendere un oggetto iterabile
Kos,

4
FWIW: ha iter(myObj)successo se isinstance(myObj, dict), quindi se stai guardando una myObjche potrebbe essere una sequenza di dicts o una singola dict, avrai successo in entrambi i casi. Una sottigliezza che è importante se vuoi sapere cos'è una sequenza e cosa no. (in Python 2)
Ben Mosher,

7
__getitem__è anche sufficiente per rendere un oggetto iterabile ... se inizia con indice zero .
Carlos A. Gómez,

Risposte:


28

Ho studiato questo problema un po 'di recente. Sulla base di ciò, la mia conclusione è che al giorno d'oggi questo è l'approccio migliore:

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

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

Quanto sopra è stato raccomandato già prima, ma il consenso generale è stato che l'uso iter()sarebbe meglio:

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

Abbiamo usato anche iter()questo codice per questo scopo, ma ultimamente ho iniziato a infastidirmi sempre più di oggetti che sono __getitem__stati considerati solo iterabili. Ci sono validi motivi per avere __getitem__in un oggetto non iterabile e con essi il codice sopra non funziona bene. Come esempio di vita reale possiamo usare Faker . Il codice sopra riportato indica che è iterabile ma che in realtà provando a iterarlo provoca un AttributeError(testato con Faker 4.0.2):

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

Se lo utilizzassimo insinstance(), non considereremmo accidentalmente le istanze di Faker (o qualsiasi altro oggetto che abbia solo __getitem__) come iterabili:

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

Le risposte precedenti hanno commentato che l'utilizzo iter()è più sicuro poiché il vecchio modo di implementare l'iterazione in Python era basato su __getitem__e l' isinstance()approccio non lo avrebbe rilevato. Questo potrebbe essere vero con le vecchie versioni di Python, ma in base ai miei test piuttosto esaustivi isinstance()funziona benissimo al giorno d'oggi. L'unico caso in cui isinstance()non ha funzionato ma iter()è stato riscontrato UserDictquando si utilizza Python 2. Se ciò è rilevante, è possibile utilizzare isinstance(item, (Iterable, UserDict))per ottenere tale copertura.


1
Inoltre typing.Dictè considerato iterabile da iter(Dict)ma list(Dict)non riesce con errore TypeError: Parameters to generic types must be types. Got 0.. Come previsto, isinstance(Dict, Iterable)restituisce false.
Pekka Klärck,

1
Sono arrivato alla stessa conclusione, ma per motivi diversi. L'uso di iteralcuni dei nostri codici che utilizzava il "pre-caching" per rallentare inutilmente. Se il __iter__codice è lento, lo sarà anche la chiamata iter... ogni volta che vuoi solo vedere se qualcosa è iterabile.
thorwhalen

842
  1. Controllare la presenza di __iter__lavori sui tipi di sequenza, ma fallirebbe ad esempio sulle stringhe in Python 2 . Vorrei sapere anche la risposta giusta, fino ad allora, ecco una possibilità (che funzionerebbe anche sulle stringhe):

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

    Il itercontrollo integrato verifica il __iter__metodo o nel caso di stringhe il __getitem__metodo.

  2. Un altro approccio pitonico generale è quello di assumere un iterabile, quindi fallire con grazia se non funziona sull'oggetto dato. Il glossario di Python:

    Stile di programmazione Pythonic che determina il tipo di un oggetto ispezionandone il metodo o la firma dell'attributo piuttosto che tramite una relazione esplicita con un oggetto di tipo ("Se sembra un papero e cigola come un papero , deve essere un papero ") Sottolineando le interfacce piuttosto che tipi specifici, un codice ben progettato migliora la sua flessibilità consentendo la sostituzione polimorfica. La tipizzazione anatra evita i test usando type () o isinstance (). Invece, in genere utilizza lo stile di programmazione EAFP (Più facile chiedere perdono che autorizzazione).

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
  3. Il collectionsmodulo fornisce alcune classi di base astratte, che consentono di chiedere a classi o istanze se forniscono funzionalità particolari, ad esempio:

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

    Tuttavia, ciò non verifica la presenza di classi iterabili __getitem__.


34
[e for e in my_object]può sollevare un'eccezione per altri motivi, ovvero my_objectnon è definito o è possibile che si verifichino bug my_objectnell'attuazione.
Nick Dandoulakis,

37
Una stringa è una sequenza ( isinstance('', Sequence) == True) e come qualsiasi sequenza è iterabile ( isinstance('', Iterable)). Anche se hasattr('', '__iter__') == Falsee potrebbe essere fonte di confusione.
jfs,

82
Se my_objectè molto grande (diciamo, infinito come itertools.count()) la comprensione della tua lista richiederà molto tempo / memoria. Meglio creare un generatore, che non tenterà mai di costruire un elenco (potenzialmente infinito).
Chris Lutz,

14
Cosa succede se some_object genera TypeError anche per altri motivi (bug, ecc.)? Come possiamo dirlo dal "TypeError non iterabile"?
Shaung,

54
Nota che in Python 3: hasattr(u"hello", '__iter__')ritornaTrue
Carlos,

573

Duck typing

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

# for obj in iterator:
#     pass

Tipo di controllo

Usa le classi base astratte . Hanno bisogno almeno di Python 2.6 e funzionano solo per le classi di nuovo stile.

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

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

Tuttavia, iter()è un po 'più affidabile come descritto dalla documentazione :

Il controllo isinstance(obj, Iterable)rileva le classi registrate come Iterable o che dispongono di un __iter__()metodo, ma non rileva le classi che ripetono con il __getitem__() metodo. L'unico modo affidabile per determinare se un oggetto è iterabile è chiamare iter(obj).


18
Da "Fluent Python" di Luciano Ramalho: A partire da Python 3.4, il modo più accurato per verificare se un oggetto x è iterabile è chiamare iter (x) e gestire un'eccezione TypeError se non lo è. Questo è più preciso dell'uso di isinstance (x, abc.Iterable), perché iter (x) considera anche il metodo getitem legacy , mentre Iterable ABC no.
RdB,

Nel caso in cui tu stia pensando "oh, io isinstance(x, (collections.Iterable, collections.Sequence))invece di iter(x)", nota che questo non rileverà ancora un oggetto iterabile che implementa solo __getitem__ma non __len__. Usa iter(x)e cattura l'eccezione.
Dale,

La tua seconda risposta non funziona. In PyUNO, se lo faccio iter(slide1), tutto va bene, comunque i isinstance(slide1, Iterable)tiri TypeError: issubclass() arg 1 must be a class.
Hi-Angel,

@ Hi-Angel suona come un bug in PyUNONotare che il tuo messaggio di errore dice issubclass()invece di isinstance().
Georg Schölly,

2
Chiamare iter () su un oggetto può essere un'operazione costosa (vedi DataLoader in Pytorch, che esegue il fork / spawn di più processi su iter ()).
szali,

126

Mi piacerebbe gettare un po 'di più in luce l'interazione di iter, __iter__ed __getitem__e ciò che accade dietro le tende. Grazie a questa conoscenza, sarai in grado di capire perché il meglio che puoi fare è

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

Elencherò prima i fatti e poi seguirò con un breve promemoria di ciò che accade quando si utilizza un forloop in Python, seguito da una discussione per illustrare i fatti.

I fatti

  1. Puoi ottenere un iteratore da qualsiasi oggetto ochiamando iter(o)se almeno una delle seguenti condizioni è vera:

    a) oha un __iter__metodo che restituisce un oggetto iteratore. Un iteratore è qualsiasi oggetto con un __iter__e un metodo __next__(Python 2 next:).

    b) oha un __getitem__metodo.

  2. Controllare un'istanza di Iterableo Sequenceoppure verificare l'attributo __iter__non è sufficiente.

  3. Se un oggetto oimplementa solo __getitem__, ma non __iter__, iter(o)costruirà un iteratore che tenta di recuperare elementi odall'indice intero, iniziando dall'indice 0. L'iteratore catturerà qualsiasi IndexError(ma nessun altro errore) che viene generato e quindi si solleva StopIteration.

  4. Nel senso più generale, non c'è modo di verificare se l'iteratore restituito iterè sano se non quello di provarlo.

  5. Se un oggetto viene oimplementato __iter__, la iterfunzione assicurerà che l'oggetto restituito __iter__sia un iteratore. Non esiste un controllo di integrità se un oggetto implementa solo __getitem__.

  6. __iter__vince. Se un oggetto oimplementa entrambi __iter__e __getitem__, iter(o)chiamerà __iter__.

  7. Se si desidera rendere iterabili i propri oggetti, implementare sempre il __iter__metodo

for loop

Per poter seguire, è necessario comprendere cosa succede quando si utilizza un forloop in Python. Sentiti libero di saltare direttamente alla sezione successiva se lo sai già.

Quando si utilizza for item in oper qualche oggetto iterabile o, Python chiama iter(o)e si aspetta un oggetto iteratore come valore restituito. Un iteratore è qualsiasi oggetto che implementa un metodo __next__(o nextin Python 2) e un __iter__metodo.

Per convenzione, il __iter__metodo di un iteratore dovrebbe restituire l'oggetto stesso (cioè return self). Python quindi chiama nextl'iteratore fino a quando non StopIterationviene generato. Tutto ciò accade implicitamente, ma la seguente dimostrazione lo rende visibile:

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

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

Discussione e illustrazioni

Sui punti 1 e 2: ottenere un iteratore e controlli inaffidabili

Considera la seguente classe:

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

Chiamare itercon un'istanza di BasicIterablerestituirà un iteratore senza problemi perché BasicIterableimplementa __getitem__.

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

Tuttavia, è importante notare che bnon ha l' __iter__attributo e non è considerato un'istanza di Iterableo Sequence:

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

Ecco perché Fluent Python di Luciano Ramalho raccomanda di chiamare itere gestire il potenziale TypeErrorcome il modo più accurato per verificare se un oggetto è iterabile. Citando direttamente dal libro:

A partire da Python 3.4, il modo più accurato per verificare se un oggetto xè iterabile è chiamare iter(x)e gestire TypeErrorun'eccezione se non lo è. Questo è più preciso dell'uso isinstance(x, abc.Iterable), perché iter(x)considera anche il __getitem__metodo legacy , mentre l' IterableABC no.

Al punto 3: Iterazione su oggetti che forniscono solo __getitem__, ma non__iter__

Iterazione su un'istanza di BasicIterablelavori come previsto: Python costruisce un iteratore che tenta di recuperare gli elementi per indice, iniziando da zero, fino a quando non IndexErrorviene generato un. Il __getitem__metodo dell'oggetto demo restituisce semplicemente ciò itemche è stato fornito come argomento __getitem__(self, item)dall'iteratore restituito da 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

Si noti che l'iteratore aumenta StopIterationquando non è in grado di restituire l'elemento successivo e quello per IndexErrorcui viene sollevato item == 3viene gestito internamente. Questo è il motivo per cui il looping su a BasicIterablecon un forloop funziona come previsto:

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

Ecco un altro esempio per portare a casa il concetto di come l'iteratore restituito itertenta di accedere agli elementi per indice. WrappedDictnon eredita da dict, il che significa che le istanze non avranno un __iter__metodo.

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

Si noti che le chiamate a __getitem__vengono delegate dict.__getitem__per le quali la notazione tra parentesi quadre è semplicemente una scorciatoia.

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

Ai punti 4 e 5: iterverifica la presenza di un iteratore quando chiama__iter__ :

Quando iter(o)viene chiamato per un oggetto o, itersi assicurerà che il valore restituito di __iter__, se il metodo è presente, sia un iteratore. Ciò significa che l'oggetto restituito deve essere implementato __next__(o nextin Python 2) e __iter__. iternon può eseguire alcun controllo di integrità per gli oggetti che forniscono solo __getitem__, poiché non ha modo di verificare se gli oggetti dell'oggetto sono accessibili tramite l'indice intero.

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

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

Si noti che la costruzione di un iteratore dalle FailIterIterableistanze non riesce immediatamente, mentre la costruzione di un iteratore ha esito FailGetItemIterablepositivo, ma genererà un'eccezione alla prima chiamata 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

Al punto 6: __iter__vince

Questo è semplice. Se un oggetto implementa __iter__e __getitem__, iterchiamerà __iter__. Considera la seguente classe

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

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

e l'output durante il loop su un'istanza:

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

Al punto 7: le tue classi iterabili dovrebbero essere implementate __iter__

Potresti chiederti perché la maggior parte delle sequenze integrate come listimplementare un __iter__metodo quando __getitem__sarebbe sufficiente.

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

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

Dopotutto, l'iterazione sulle istanze della classe sopra, che i delegati chiamano __getitem__a list.__getitem__(usando la notazione con parentesi quadra), funzionerà bene:

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

I motivi per cui i tuoi iterable personalizzati devono essere implementati __iter__sono i seguenti:

  1. Se si implementa __iter__, le istanze verranno considerate iterabili e isinstance(o, collections.abc.Iterable)verranno restituite True.
  2. Se l'oggetto restituito da __iter__non è un iteratore, iterfallirà immediatamente e genererà a TypeError.
  3. La gestione speciale di __getitem__esiste per motivi di compatibilità con le versioni precedenti. Citando di nuovo da Fluent Python:

Ecco perché qualsiasi sequenza di Python è iterabile: tutti implementano __getitem__. In effetti, anche le sequenze standard implementano __iter__, e anche le tue, perché la gestione speciale di __getitem__esiste per motivi di compatibilità con le versioni precedenti e potrebbe essere andata in futuro (anche se non è deprecata mentre scrivo questo).


quindi è sicuro definire un predicato is_iterableritornando Truenel tryblocco e Falsenel except TypeErrorblocco?
alancalvitti,

Questa è un'ottima risposta Penso che evidenzi la natura non intuitiva e sfortunata del protocollo getitem. Non avrebbe mai dovuto essere aggiunto.
Neil G,

31

Questo non è sufficiente: l'oggetto restituito da __iter__deve implementare il protocollo di iterazione (cioè nextmetodo). Vedere la sezione pertinente nella documentazione .

In Python, una buona pratica è "provare a vedere" invece di "controllare".


9
"digitando anatra" credo? :)
willem,

9
@willem: o "non chiedere il permesso ma il perdono" ;-)
jldupont

14
@willem Entrambi gli stili "permesso" e "perdono" si qualificano come tipizzazione anatra. Se chiedi cosa può fare un oggetto piuttosto che cos'è, si tratta di scrivere l'anatra. Se usi l'introspezione, questo è "permesso"; se provi semplicemente a farlo e vedi se funziona o no, questo è "perdono".
Mark Reed,

22

In Python <= 2.5, non puoi e non dovresti - iterable era un'interfaccia "informale".

Ma da Python 2.6 e 3.0 puoi sfruttare la nuova infrastruttura ABC (abstract base class) insieme ad alcuni ABC incorporati che sono disponibili nel modulo collezioni:

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)

Ora, se questo è desiderabile o effettivamente funziona, è solo una questione di convenzioni. Come puoi vedere, puoi registrare un oggetto non iterabile come Iterable e genererà un'eccezione in fase di esecuzione. Quindi, isinstance acquisisce un significato "nuovo" - controlla solo la compatibilità del tipo "dichiarato", che è un buon modo per andare in Python.

D'altra parte, se il tuo oggetto non soddisfa l'interfaccia di cui hai bisogno, cosa hai intenzione di fare? Prendi il seguente esempio:

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 l'oggetto non soddisfa ciò che ti aspetti, ti basta lanciare un TypeError, ma se è stato registrato l'ABC corretto, il tuo controllo è inutile. Al contrario, se il __iter__metodo è disponibile, Python riconoscerà automaticamente l'oggetto di quella classe come Iterable.

Quindi, se ti aspetti solo un iterabile, ripeti e dimenticalo. D'altra parte, se devi fare cose diverse a seconda del tipo di input, potresti trovare l'infrastruttura ABC abbastanza utile.


13
non usare nudo except:nel codice di esempio per i principianti. Promuove cattive pratiche.
jfs,

JFS: Non lo farei, ma avevo bisogno di esaminare più codici che generano eccezioni e non volevo cogliere l'eccezione specifica ... Penso che lo scopo di questo codice sia abbastanza chiaro.
Alan Franzoni,

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

Non eseguire controlli per vedere se la tua anatra è davvero un'anatra per vedere se è iterabile o meno, trattala come se fosse e lamentati se non lo fosse.


3
Tecnicamente, durante l'iterazione il tuo calcolo potrebbe buttarti TypeErrore buttarti via qui, ma sostanzialmente sì.
Chris Lutz,

6
@willem: utilizzare timeit per eseguire un benchmark. Le eccezioni di Python sono spesso più veloci delle istruzioni if. Possono prendere un percorso leggermente più breve attraverso l'interprete.
S.Lott

2
@willem: IronPython ha eccezioni lente (rispetto a CPython).
jfs,

2
Una prova di lavoro: l'affermazione è davvero veloce. Quindi, se hai poche eccezioni, provare-tranne è veloce. Se ti aspetti molte eccezioni, "if" può essere più veloce.
Arne Babenhauserheide,

2
L'oggetto eccezione non dovrebbe essere catturato aggiungendo " as e" dopo TypeErroranziché aggiungendo " , e"?
Ciao Arrivederci,

21

Da Python 3.5 puoi usare il modulo di digitazione dalla libreria standard per cose relative al tipo:

from typing import Iterable

...

if isinstance(my_item, Iterable):
    print(True)

18

La migliore soluzione che ho trovato finora:

hasattr(obj, '__contains__')

che sostanzialmente controlla se l'oggetto implementa l' inoperatore.

Vantaggi (nessuna delle altre soluzioni ha tutte e tre):

  • è un'espressione (funziona come una lambda , al contrario del tentativo ... tranne la variante)
  • è (dovrebbe essere) implementato da tutti gli iterabili, incluse le stringhe (al contrario di __iter__)
  • funziona su qualsiasi Python> = 2.5

Appunti:

  • la filosofia Python di "chiedere perdono, non permesso" non funziona bene quando, ad esempio, in un elenco sono presenti sia iterabili che non iterabili e è necessario trattare ogni elemento in modo diverso in base al tipo (trattamento degli iterabili su try e non- iterabili su tranne avrebbe funzionato, ma sarebbe guardare butt-brutto e fuorviante)
  • le soluzioni a questo problema che tentano di iterare effettivamente sull'oggetto (ad es. [x per x in obj]) per verificare se è iterabile possono indurre penali di prestazione significative per iterabili di grandi dimensioni (specialmente se sono necessari solo i primi elementi dell'iterabile, per esempio) e dovrebbe essere evitato

3
Bello, ma perché non usare il modulo collezioni come proposto in stackoverflow.com/questions/1952464/… ? Mi sembra più espressivo.
Dave Abrahams,

1
È più breve (e non richiede ulteriori importazioni) senza perdere la chiarezza: avere un metodo "contiene" sembra un modo naturale per verificare se qualcosa è una raccolta di oggetti.
Vlad,

46
Solo perché qualcosa può contenere qualcosa non significa necessariamente che sia iterabile. Ad esempio, un utente può verificare se un punto si trova in un cubo 3D, ma come si fa a scorrere questo oggetto?
Casey Kuball,

13
Questo non è corretto Un iterabile in sé non supporta contiene , almeno con Python 3.4.
Peter Shinners,

15

Puoi provare questo:

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

Se riusciamo a creare un generatore che scorre su di esso (ma non usiamo mai il generatore in modo da non occupare spazio), è iterabile. Sembra una specie di "duh". Perché è necessario determinare se una variabile è iterabile in primo luogo?


Che dire iterable(itertools.repeat(0))? :)
badp,

5
@badp, il (x for x in a)giusto crea un generatore, non esegue alcuna iterazione su a.
catchmeifyoutry,

5
Provare è (x for x in a)esattamente equivalente a provare iterator = iter(a)? O ci sono alcuni casi in cui i due sono diversi?
massimo

Non è for _ in a: breakpiù semplice? È più lento?
Mr_and_Mrs_D,

2
@Mr_and_Mrs_D è un male se l'oggetto testato è un iteratore che è stato ripetuto in seguito (sarà breve di 1 elemento poiché la sua posizione non può essere ripristinata), la creazione di generatori di immondizia non itera sull'oggetto in quanto non sono ripetuti, anche se non sono sicuro che aumenterà al 100% un TypeError se non è iterabile.
Fino al

13

Ho trovato una bella soluzione qui :

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

11

Secondo il Glossario di Python 2 , gli iterabili sono

tutti i tipi di sequenza (come list, stre tuple) e alcuni tipi non di sequenza come dicte filee oggetti di qualsiasi classe definita con un metodo __iter__()o __getitem__(). Iterables può essere utilizzato in un ciclo for e in molti altri luoghi in cui è necessaria una sequenza (zip (), map (), ...). Quando un oggetto iterabile viene passato come argomento alla funzione integrata iter (), restituisce un iteratore per l'oggetto.

Naturalmente, dato lo stile di codifica generale per Python basato sul fatto che è "Più facile chiedere perdono che permesso", l'aspettativa generale è di usare

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

Ma se è necessario verificarlo esplicitamente, è possibile verificare un iterabile da hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). Devi controllare entrambi, perché strnon hanno un __iter__metodo (almeno non in Python 2, in Python 3 lo fanno) e perché gli generatoroggetti non hanno un __getitem__metodo.


8

Spesso trovo conveniente, all'interno dei miei script, definire una iterablefunzione. (Ora incorpora la semplificazione suggerita di Alfe):

import collections

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

così puoi verificare se qualsiasi oggetto è iterabile nella forma molto leggibile

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

come faresti con la callablefunzione

EDIT: se hai installato numpy, puoi semplicemente fare: da numpy import iterable, che è semplicemente qualcosa di simile

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

Se non hai intorpidimento, puoi semplicemente implementare questo codice o quello sopra.


3
Ogni volta che lo fai come if x: return True else: return False(con l' xessere booleano) puoi scrivere come return x. Nel tuo caso return isinstance(…)senza nessuno if.
Alfe,

Dato che riconosci che la soluzione di Alfe è migliore, perché non hai modificato la tua risposta per dirlo semplicemente? Invece, ora hai ENTRAMBE le versioni nella tua risposta. Verbosità inutile. Invio di una modifica per risolvere il problema.
ToolmakerSteve

2
Dovresti prendere "TypeError" nella riga ʻ tranne: return False `. Catturare tutto è un cattivo modello.
Mariusz Jamro,

Sapere che. Ho tradotto quel pezzo di codice dalla libreria NumPy, che utilizza l'eccezione generica.
fmonegaglia,

Solo perché un codice è preso da NumPy non significa che sia buono ... modello o no, l'unica volta che si dovrebbe fare tutto ciò che è necessario è se si sta esplicitamente gestendo gli errori all'interno del proprio programma.
Fino al

5

ha una funzione integrata come quella:

from pandas.util.testing import isiterable

questo però sembra solo se ci sono __iter__e non si preoccupano davvero di sequenze e simili.
Mead

4

Mi è sempre sfuggito il motivo per cui Python ha callable(obj) -> boolma non iterable(obj) -> bool...
sicuramente è più facile da farehasattr(obj,'__call__') anche se è più lento.

Poiché quasi tutte le altre risposte raccomandano l'uso di try/ except TypeError, dove il test delle eccezioni è generalmente considerato una cattiva pratica in qualsiasi lingua, ecco un'implementazione di cui iterable(obj) -> boolmi sono sempre più affezionato e uso spesso:

Per amor di python 2, userò un lambda solo per quel boost di prestazioni extra ...
(in python 3 non importa cosa usi per definire la funzione, defha all'incirca la stessa velocità di lambda)

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

Si noti che questa funzione viene eseguita più velocemente per gli oggetti __iter__poiché non è stata testata __getitem__.

La maggior parte degli oggetti iterabili dovrebbe fare affidamento su __iter__dove ricorrono gli oggetti in casi speciali __getitem__, sebbene sia necessario che uno degli oggetti sia iterabile.
(e poiché questo è standard, influenza anche gli oggetti C)


non fornisce codice funzionante, per non parlare delle prestazioni di Python ... anche se questa risposta è stata davvero solo per comodità, come ho visto fare più volte qui.
Fino al

3
def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

Questo dirà sì a tutti i tipi di oggetti iterabili, ma dirà no alle stringhe in Python 2 . (È quello che voglio, ad esempio, quando una funzione ricorsiva può prendere una stringa o un contenitore di stringhe. In quella situazione, chiedere perdono può portare all'obfuscode, ed è meglio chiedere prima il permesso.)

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)

Molte altre strategie qui diranno sì alle stringhe. Usali se è quello che vuoi.

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ì alle stringhe di tipo bytese bytearray.

  • bytesgli oggetti in Python 3 sono iterabili True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))Non esiste tale tipo in Python 2.
  • bytearray gli oggetti in Python 2 e 3 sono iterabili True == is_iterable(bytearray(b"abc"))

L' hasattr(x, '__iter__')approccio OP dirà sì alle stringhe in Python 3 e no in Python 2 (non importa se ''o b''o u''). Grazie a @LuisMasuelli per averlo notato, ti deluderà anche su un passeggino __iter__.


2

Il modo più semplice, rispettando la tipizzazione anatra di Python , è catturare l'errore (Python sa perfettamente cosa si aspetta da un oggetto per diventare un iteratore):

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

Note :

  1. È irrilevante la distinzione se l'oggetto non è iterabile o se __iter__è stato implementato un buggy se il tipo di eccezione è lo stesso: in ogni caso non sarà possibile iterare l'oggetto.
  2. Credo di comprendere la tua preoccupazione: come callableesiste un controllo se potessi anche fare affidamento sulla digitazione duck per sollevare un AttributeErrorif se __call__non è definito per il mio oggetto, ma non è questo il caso del controllo iterabile?

    Non conosco la risposta, ma puoi implementare la funzione che io (e altri utenti) ho dato o semplicemente catturare l'eccezione nel tuo codice (l'implementazione in quella parte sarà come la funzione che ho scritto - assicurati solo di isolare il creazione dell'iteratore dal resto del codice in modo da poter catturare l'eccezione e distinguerla da un'altra TypeError.


1

La isiterablefunzione al seguente codice restituisce Truese l'oggetto è iterabile. se non è restituibile iterabileFalse

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

esempio

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

2
così tante risposte dettagliate sopra con molti voti e si lancia in una risposta inspiegabile ... meh
Nrzonline

Si prega di non pubblicare codice nudo. Includi anche una spiegazione di ciò che sta facendo.
Jonathan Mee,

1

Invece di verificare l' __iter__attributo, è possibile verificare l' __len__attributo, che è implementato da ogni Python incorporato iterabile, comprese le stringhe.

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

Oggetti non iterabili non implementerebbero questo per ovvi motivi. Tuttavia, non rileva gli iterable definiti dall'utente che non lo implementano, né le espressioni del generatore, che iterpossono essere gestite. Tuttavia, ciò può essere fatto in una riga e l'aggiunta di una semplice orespressione che controlla i generatori risolverà questo problema. (Si noti che la scrittura type(my_generator_expression) == generatorgenererebbe a NameError. Consultare invece questa risposta.)

È possibile utilizzare GeneratorType dai tipi:

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

--- risposta accettata da utdemir

(Questo lo rende utile per verificare se è possibile chiamare lenl'oggetto.)


purtroppo non tutti gli oggetti iterabili usano __len__... per questo caso, di solito è l'uso improprio del calcolo della distanza tra 2 oggetti. dove obj.dist()potrebbe essere facilmente sostituito.
Fino al

Si. La maggior parte degli iterabili definiti dall'utente implementano iter e getitem ma non len. Tuttavia, i tipi predefiniti lo fanno e se si desidera verificare se è possibile chiamare la funzione len su di essa, il controllo di len è più sicuro. Ma hai ragione.
DarthCadeus,

0

Non proprio "corretto" ma può servire come controllo rapido dei tipi più comuni come stringhe, tuple, float, ecc ...

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

0

Un po 'tardi alla festa, ma mi sono posto questa domanda e ho visto questo, poi ho pensato a una risposta. Non so se qualcuno lo abbia già pubblicato. Ma essenzialmente, ho notato che tutti i tipi iterabili hanno __getitem __ () nel loro dict. Ecco come verifichi se un oggetto fosse iterabile senza nemmeno provarci. (Gioco di parole previsto)

def is_attr(arg):
    return '__getitem__' in dir(arg)

Sfortunatamente, questo non è affidabile. Esempio
timgeb,

1
Gli oggetti impostati sono un altro controesempio.
Raymond Hettinger,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.