Wie bestimme ich in Python, ob ein Objekt iterierbar ist?

1083

Gibt es eine Methode wie isiterable? Die einzige Lösung, die ich bisher gefunden habe, ist anzurufen

hasattr(myObj, '__iter__')

Aber ich bin mir nicht sicher, wie narrensicher das ist.

Willem
quelle
18
__getitem__ist auch ausreichend, um ein Objekt iterierbar zu machen
Kos
4
FWIW: ist iter(myObj)erfolgreich isinstance(myObj, dict), wenn Sie myObjalso in beiden Fällen erfolgreich sind , wenn Sie sich eine Sequenz von dicts oder eine einzelne ansehen dict. Eine Subtilität, die wichtig ist, wenn Sie wissen möchten, was eine Sequenz ist und was nicht. (in Python 2)
Ben Mosher
7
__getitem__ist auch ausreichend, um ein Objekt iterierbar zu machen ... wenn es bei Null beginnt .
Carlos A. Gómez

Antworten:

26

Ich habe dieses Problem in letzter Zeit ziemlich viel untersucht. Aufgrund dessen komme ich zu dem Schluss, dass dies heutzutage der beste Ansatz ist:

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

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

Das oben Gesagte wurde bereits früher empfohlen, aber der allgemeine Konsens war, dass die Verwendung iter()besser wäre:

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

Wir haben iter()unseren Code auch für diesen Zweck verwendet, aber in letzter Zeit ärgere ich mich immer mehr über Objekte, die nur __getitem__als iterierbar angesehen wurden. Es gibt triftige Gründe für __getitem__ein nicht iterierbares Objekt, und mit ihnen funktioniert der obige Code nicht gut. Als Beispiel aus dem wirklichen Leben können wir Faker verwenden . Der obige Code gibt an, dass es iterierbar ist, aber der Versuch, es zu iterieren, führt zu einem AttributeError(getestet mit 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'

Wenn wir verwenden würden insinstance(), würden wir Faker-Instanzen (oder andere Objekte, die nur __getitem__) nicht versehentlich als iterierbar betrachten:

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

In früheren Antworten wurde darauf hingewiesen, dass die Verwendung iter()sicherer ist, da die alte Methode zur Implementierung der Iteration in Python darauf basiert __getitem__und der isinstance()Ansatz dies nicht erkennen würde. Dies mag bei alten Python-Versionen der Fall gewesen sein, aber basierend auf meinen ziemlich umfassenden Tests isinstance()funktioniert es heutzutage großartig. Der einzige Fall, in dem isinstance()dies nicht funktioniert iter()hat, war die UserDictVerwendung von Python 2. Wenn dies relevant ist, können Sie es verwenden isinstance(item, (Iterable, UserDict)), um dies zu behandeln.

Pekka Klärck
quelle
1
Wird auch typing.Dictals iterierbar angesehen iter(Dict), list(Dict)schlägt aber mit Fehler fehl TypeError: Parameters to generic types must be types. Got 0.. Wie erwartet wird isinstance(Dict, Iterable)false zurückgegeben.
Pekka Klärck
1
Ich bin zu dem gleichen Schluss gekommen, aber aus verschiedenen Gründen. Durch iterdie Verwendung wurde ein Teil unseres Codes, der "Pre-Caching" verwendete, unnötig verlangsamt. Wenn der __iter__Code langsam ist, wird dies auch der iterFall sein ... jederzeit, wenn Sie nur sehen möchten, ob etwas iterierbar ist.
Thorwhalen
842
  1. Das Suchen nach Funktionen für __iter__Sequenztypen würde jedoch beispielsweise bei Zeichenfolgen in Python 2 fehlschlagen . Ich würde auch gerne die richtige Antwort wissen, bis dahin gibt es hier eine Möglichkeit (die auch bei Strings funktionieren würde):

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

    Das itereingebaute prüft die __iter__Methode oder bei Strings die __getitem__Methode.

  2. Ein anderer allgemeiner pythonischer Ansatz besteht darin, eine iterierbare Methode anzunehmen und dann ordnungsgemäß fehlzuschlagen, wenn sie für das angegebene Objekt nicht funktioniert. Das Python-Glossar:

    Pythonic Programmierstil , die eine Objekttyp durch Inspektion seiner Methode oder Attribut Unterschrift und nicht durch explizite Beziehung zu einem gewissen Typ Objekt bestimmt ( „Wenn es aussieht wie ein Ente und quakt wie eine Ente , muss es sein , Ente .“) Durch die Betonung Schnittstellen Anstelle bestimmter Typen verbessert gut gestalteter Code seine Flexibilität, indem er eine polymorphe Substitution ermöglicht. Duck-Typing vermeidet Tests mit type () oder isinstance (). Stattdessen wird normalerweise der EAFP-Programmierstil (einfacher um Vergebung als um Erlaubnis zu bitten) verwendet.

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
  3. Das collectionsModul bietet einige abstrakte Basisklassen, mit denen Klassen oder Instanzen gefragt werden können, ob sie bestimmte Funktionen bieten, z. B.:

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

    Dies prüft jedoch nicht, ob Klassen iterierbar sind __getitem__.

miku
quelle
34
[e for e in my_object]kann aus anderen Gründen eine Ausnahme auslösen, dh my_objectundefinierte oder mögliche Fehler bei der my_objectImplementierung.
Nick Dandoulakis
37
Ein String ist eine Folge ( isinstance('', Sequence) == True) und als beliebiger Reihenfolge es ist iterable ( isinstance('', Iterable)). Obwohl hasattr('', '__iter__') == Falseund es könnte verwirrend sein.
JFS
82
Wenn my_objectes sehr groß ist (z. B. unendlich itertools.count()), nimmt Ihr Listenverständnis viel Zeit / Speicher in Anspruch. Es ist besser, einen Generator zu erstellen, der niemals versucht, eine (möglicherweise unendliche) Liste zu erstellen.
Chris Lutz
14
Was ist, wenn some_object TypeError auslöst, das auch aus anderen Gründen (Bugs usw.) verursacht wurde? Wie können wir es vom "nicht iterierbaren TypeError" unterscheiden?
Shaung
54
Beachten Sie, dass in Python 3: hasattr(u"hello", '__iter__')zurückkehrtTrue
Carlos
572

Ente tippen

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

# for obj in iterator:
#     pass

Typprüfung

Verwenden Sie die abstrakten Basisklassen . Sie benötigen mindestens Python 2.6 und funktionieren nur für Klassen neuen Stils.

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

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

Ist iter()jedoch etwas zuverlässiger als in der Dokumentation beschrieben :

Durch das Überprüfen werden isinstance(obj, Iterable)Klassen erkannt, die als iterierbar registriert sind oder eine __iter__()Methode haben, aber keine Klassen, die mit der __getitem__() Methode iterieren . Der einzige zuverlässige Weg, um festzustellen, ob ein Objekt iterierbar ist, ist der Aufruf iter(obj).

Georg Schölly
quelle
18
Aus "Fluent Python" von Luciano Ramalho: Ab Python 3.4 können Sie am genauesten überprüfen, ob ein Objekt x iterierbar ist, indem Sie iter (x) aufrufen und eine TypeError-Ausnahme behandeln, wenn dies nicht der Fall ist. Dies ist genauer als die Verwendung von isinstance (x, abc.Iterable), da iter (x) auch die Legacy- Getitem- Methode berücksichtigt , während Iterable ABC dies nicht tut.
RdB
Wenn Sie denken "Oh, ich werde nur isinstance(x, (collections.Iterable, collections.Sequence))statt iter(x)", beachten Sie, dass dies immer noch kein iterierbares Objekt erkennt, das nur implementiert, __getitem__aber nicht __len__. Verwenden iter(x)und fangen Sie die Ausnahme.
Dale
Ihre zweite Antwort funktioniert nicht. In PyUNO, wenn ich das tue iter(slide1), geht alles gut, aber die isinstance(slide1, Iterable)Würfe TypeError: issubclass() arg 1 must be a class.
Hi-Angel
@ Hi-Angel klingt wie ein Fehler in PyUNOBeachten Sie, dass Ihre Fehlermeldung issubclass()statt sagt isinstance().
Georg Schölly
2
Das Aufrufen von iter () über ein Objekt kann eine teure Operation sein (siehe DataLoader in Pytorch, der mehrere Prozesse auf iter () abspaltet / erzeugt).
Szali
126

Ich möchte ein wenig mehr Licht biss sich auf das Zusammenspiel von iter, __iter__und , __getitem__und was hinter den Kulissen passiert. Mit diesem Wissen können Sie verstehen, warum das Beste, was Sie tun können, ist

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

Ich werde zuerst die Fakten auflisten und anschließend kurz daran erinnern, was passiert, wenn Sie eine forSchleife in Python verwenden, gefolgt von einer Diskussion, um die Fakten zu veranschaulichen.

Fakten

  1. Sie können einen Iterator von jedem Objekt abrufen, oindem Sie aufrufen, iter(o)ob mindestens eine der folgenden Bedingungen erfüllt ist:

    a) ohat eine __iter__Methode, die ein Iteratorobjekt zurückgibt. Ein Iterator ist ein beliebiges Objekt mit einer __iter__und einer __next__(Python 2 :) next-Methode.

    b) ohat eine __getitem__Methode.

  2. Es reicht nicht aus, nach einer Instanz von Iterableoder zu Sequencesuchen oder nach dem Attribut zu __iter__suchen.

  3. Wenn ein Objekt onur implementiert __getitem__, aber nicht __iter__, iter(o)wird ein Iterator erstellt, der versucht, Elemente oanhand eines ganzzahligen Index ab Index 0 abzurufen . Der Iterator fängt alle IndexError(aber keine anderen Fehler) ab, die ausgelöst werden, und löst StopIterationsich dann selbst aus.

  4. Im allgemeinsten Sinne gibt es keine iterandere Möglichkeit, zu überprüfen, ob der von zurückgegebene Iterator vernünftig ist, als ihn auszuprobieren.

  5. Wenn ein Objekt oimplementiert wird __iter__, stellt die iterFunktion sicher, dass das von zurückgegebene Objekt __iter__ein Iterator ist. Es gibt keine Überprüfung der Integrität, wenn ein Objekt nur implementiert wird __getitem__.

  6. __iter__Gewinnt. Wenn ein Objekt obeide __iter__und implementiert __getitem__, iter(o)wird aufgerufen __iter__.

  7. Wenn Sie Ihre eigenen Objekte iterierbar machen möchten, implementieren Sie immer die __iter__Methode.

for Schleifen

Um mitzumachen, müssen Sie verstehen, was passiert, wenn Sie eine forSchleife in Python verwenden. Wenn Sie es bereits wissen, können Sie direkt zum nächsten Abschnitt springen.

Wenn Sie for item in ofür ein iterierbares Objekt verwenden o, ruft Python iter(o)ein Iteratorobjekt auf und erwartet es als Rückgabewert. Ein Iterator ist ein beliebiges Objekt, das eine __next__(oder nextin Python 2) Methode und eine __iter__Methode implementiert .

Konventionell sollte die __iter__Methode eines Iterators das Objekt selbst zurückgeben (dh return self). Python ruft dann nextden Iterator auf, bis er ausgelöst StopIterationwird. All dies geschieht implizit, aber die folgende Demonstration macht es sichtbar:

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

Iteration über a 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

Diskussion und Illustrationen

Zu Punkt 1 und 2: Erhalten eines Iterators und unzuverlässiger Überprüfungen

Betrachten Sie die folgende Klasse:

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

Das Aufrufen itermit einer Instanz von BasicIterablegibt einen Iterator ohne Probleme zurück, da BasicIterableimplementiert __getitem__.

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

Es ist jedoch wichtig zu beachten, dass bdas __iter__Attribut nicht vorhanden ist und nicht als Instanz von Iterableoder betrachtet wird Sequence:

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

Aus diesem Grund empfiehlt Fluent Python von Luciano Ramalho, iterdas Potenzial aufzurufen und zu behandeln TypeError, um zu überprüfen, ob ein Objekt iterierbar ist. Zitat direkt aus dem Buch:

Ab Python 3.4 können Sie am genauesten überprüfen, ob ein Objekt xiterierbar ist, indem Sie iter(x)eine TypeErrorAusnahme aufrufen und behandeln, wenn dies nicht der Fall ist. Dies ist genauer als die Verwendung isinstance(x, abc.Iterable), da iter(x)auch die Legacy- __getitem__Methode berücksichtigt wird, während dies beim IterableABC nicht der Fall ist.

Zu Punkt 3: Iterieren über Objekte, die nur liefern __getitem__, aber nicht__iter__

Durchlaufen einer Instanz von BasicIterableArbeiten wie erwartet: Python erstellt einen Iterator, der versucht, Elemente anhand des Index abzurufen, beginnend bei Null, bis ein ausgelöst IndexErrorwird. Die __getitem__Methode des Demo-Objekts gibt einfach das zurück, itemwas __getitem__(self, item)vom Iterator, der von zurückgegeben wurde, als Argument angegeben wurde 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

Beachten Sie, dass der Iterator ausgelöst wird, StopIterationwenn er das nächste Element nicht zurückgeben kann, und dass das IndexError, für item == 3das ausgelöst wird, intern behandelt wird. Aus diesem Grund funktioniert das Schleifen über a BasicIterablemit einer forSchleife wie erwartet:

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

Hier ist ein weiteres Beispiel, um das Konzept zu verdeutlichen, wie der von zurückgegebene Iterator iterversucht, über den Index auf Elemente zuzugreifen. WrappedDicterbt nicht von dict, was bedeutet, dass Instanzen keine __iter__Methode haben.

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

Beachten Sie, dass Aufrufe an __getitem__delegiert werden, dict.__getitem__für die die eckige Klammer nur eine Abkürzung ist.

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

Zu Punkt 4 und 5: iterÜberprüft beim Aufrufen nach einem Iterator__iter__ :

Wenn iter(o)für ein Objekt aufgerufen wird o, iterwird sichergestellt, dass der Rückgabewert von __iter__, wenn die Methode vorhanden ist, ein Iterator ist. Dies bedeutet, dass das zurückgegebene Objekt __next__(oder nextin Python 2) und implementieren muss __iter__. iterEs können keine Integritätsprüfungen für Objekte durchgeführt werden, die nur bereitstellen __getitem__, da nicht überprüft werden kann, ob auf die Elemente des Objekts über einen Ganzzahlindex zugegriffen werden kann.

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

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

Beachten Sie, dass das Erstellen eines Iterators aus FailIterIterableInstanzen sofort fehlschlägt, während das Erstellen eines Iterators aus Instanzen FailGetItemIterableerfolgreich ist, aber beim ersten Aufruf von eine Ausnahme auslöst __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

Zu Punkt 6: __iter__gewinnt

Dieser ist unkompliziert. Wenn ein Objekt implementiert __iter__und __getitem__, iterwird aufgerufen __iter__. Betrachten Sie die folgende Klasse

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

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

und die Ausgabe beim Durchlaufen einer Instanz:

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

Zu Punkt 7: Ihre iterierbaren Klassen sollten implementiert werden __iter__

Sie könnten sich fragen, warum die meisten eingebauten Sequenzen wie das listImplementieren einer __iter__Methode __getitem__ausreichen würden.

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

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

Immerhin Iteration über Instanzen der Klasse oben, die Delegierten Anrufe __getitem__an list.__getitem__(die eckige Klammer - Notation), werden gut funktionieren:

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

Die Gründe, die Ihre benutzerdefinierten Iterables implementieren sollten, __iter__sind folgende:

  1. Wenn Sie implementieren __iter__, werden Instanzen als iterabel betrachtet und isinstance(o, collections.abc.Iterable)zurückgegeben True.
  2. Wenn das von zurückgegebene Objekt __iter__kein Iterator ist, iterschlägt dies sofort fehl und löst a aus TypeError.
  3. Die spezielle Behandlung von __getitem__besteht aus Gründen der Abwärtskompatibilität. Nochmals aus Fluent Python zitieren:

Deshalb ist jede Python-Sequenz iterierbar: Sie alle implementieren __getitem__. Tatsächlich werden auch die Standardsequenzen implementiert __iter__, und Ihre sollten dies auch tun, da die spezielle Behandlung von __getitem__aus Gründen der Abwärtskompatibilität besteht und möglicherweise in Zukunft weg sein wird (obwohl sie beim Schreiben nicht veraltet ist).

timgeb
quelle
Es ist also sicher, ein Prädikat zu definieren, is_iterableindem Sie Truein den tryBlock und Falsein den except TypeErrorBlock zurückkehren.
Alancalvitti
Dies ist eine großartige Antwort. Ich denke, es unterstreicht die unintuitive und unglückliche Natur des getitem-Protokolls. Es sollte niemals hinzugefügt worden sein.
Neil G
31

Dies ist nicht ausreichend: Das von zurückgegebene Objekt __iter__muss das Iterationsprotokoll (dh die nextMethode) implementieren . Siehe den entsprechenden Abschnitt in der Dokumentation .

In Python empfiehlt es sich, "zu versuchen und zu sehen" anstatt "zu überprüfen".

jldupont
quelle
9
"Ente tippen" Ich glaube? :)
Willem
9
@willem: oder "bitte nicht um Erlaubnis, sondern um Vergebung" ;-)
jldupont
14
@willem Sowohl die Stile "Erlaubnis" als auch "Vergebung" gelten als Ententypisierung. Wenn Sie fragen, was ein Objekt tun kann , anstatt was es ist , dann ist das Entenschreiben. Wenn Sie Introspektion verwenden, ist das "Erlaubnis"; Wenn Sie nur versuchen, es zu tun und zu sehen, ob es funktioniert oder nicht, ist das "Vergebung".
Mark Reed
22

In Python <= 2.5 können und sollten Sie nicht - iterable war eine "informelle" Schnittstelle.

Seit Python 2.6 und 3.0 können Sie jedoch die neue ABC-Infrastruktur (Abstract Base Class) zusammen mit einigen integrierten ABCs nutzen, die im Sammlungsmodul verfügbar sind:

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)

Ob dies wünschenswert ist oder tatsächlich funktioniert, ist nur eine Frage der Konventionen. Wie Sie sehen können, Sie können eine nicht iterable Objekt als Iterable registrieren - und es wird eine Ausnahme zur Laufzeit erhöhen. Daher erhält isinstance eine "neue" Bedeutung - es prüft lediglich die "deklarierte" Typkompatibilität, was in Python ein guter Weg ist.

Was werden Sie tun, wenn Ihr Objekt die von Ihnen benötigte Schnittstelle nicht erfüllt? Nehmen Sie das folgende Beispiel:

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

Wenn das Objekt nicht Ihren Erwartungen entspricht, werfen Sie einfach einen TypeError aus. Wenn jedoch das richtige ABC registriert wurde, ist Ihre Prüfung nicht sinnvoll. Im Gegenteil, wenn die __iter__Methode verfügbar ist, erkennt Python das Objekt dieser Klasse automatisch als iterierbar.

Wenn Sie also nur ein Iterable erwarten, iterieren Sie darüber und vergessen Sie es. Wenn Sie jedoch je nach Eingabetyp unterschiedliche Aufgaben ausführen müssen, ist die ABC-Infrastruktur möglicherweise sehr nützlich.

Alan Franzoni
quelle
13
Verwenden Sie except:im Beispielcode kein Bare für Anfänger. Es fördert schlechte Praxis.
JFS
JFS: Das würde ich nicht, aber ich musste mehrere Codes zum Auslösen von Ausnahmen durchgehen und wollte die spezifische Ausnahme nicht abfangen ... Ich denke, der Zweck dieses Codes ist ziemlich klar.
Alan Franzoni
21
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

Führen Sie keine Überprüfungen durch, um festzustellen, ob Ihre Ente wirklich eine Ente ist, um festzustellen, ob sie iterierbar ist oder nicht. Behandeln Sie sie so, als ob sie es wäre, und beschweren Sie sich, wenn dies nicht der Fall ist.

badp
quelle
3
Technisch gesehen könnte Ihre Berechnung während der Iteration ein werfen TypeErrorund Sie hier abwerfen, aber im Grunde ja.
Chris Lutz
6
@willem: Bitte verwenden Sie timeit, um einen Benchmark durchzuführen. Python-Ausnahmen sind oft schneller als if-Anweisungen. Sie können einen etwas kürzeren Weg durch den Dolmetscher nehmen.
S.Lott
2
@willem: IronPython hat langsame (im Vergleich zu CPython) Ausnahmen.
JFS
2
Ein funktionierender Versuch: Aussage ist wirklich schnell. Wenn Sie also nur wenige Ausnahmen haben, ist try-exception schnell. Wenn Sie viele Ausnahmen erwarten, kann das „Wenn“ schneller sein.
Arne Babenhauserheide
2
Sollte das Ausnahmeobjekt nicht durch Hinzufügen von " as e" nach TypeErrorstatt durch Hinzufügen von " , e" abgefangen werden ?
HelloGoodbye
21

Seit Python 3.5 können Sie das Typisierungsmodul aus der Standardbibliothek für typbezogene Dinge verwenden:

from typing import Iterable

...

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

Die beste Lösung, die ich bisher gefunden habe:

hasattr(obj, '__contains__')

Dies prüft grundsätzlich, ob das Objekt den inOperator implementiert .

Vorteile (keine der anderen Lösungen hat alle drei):

  • es ist ein Ausdruck (funktioniert als Lambda , im Gegensatz zum Versuch ... außer Variante)
  • Es wird (sollte) von allen Iterables implementiert, einschließlich Strings (im Gegensatz zu __iter__).
  • funktioniert auf jedem Python> = 2.5

Anmerkungen:

  • Die Python-Philosophie "Bitte um Vergebung, nicht um Erlaubnis" funktioniert nicht gut, wenn z. B. in einer Liste sowohl iterable als auch nicht iterable Elemente vorhanden sind und Sie jedes Element je nach Typ unterschiedlich behandeln müssen (Behandlung von iterables bei try und non- iterables on außer würde funktionieren, aber es würde hintern hässlich und irreführend aussehen)
  • Lösungen für dieses Problem, bei denen versucht wird, das Objekt tatsächlich zu iterieren (z. B. [x für x in obj]), um zu überprüfen, ob es iterierbar ist, können zu erheblichen Leistungseinbußen bei großen iterablen Objekten führen (insbesondere, wenn Sie nur die ersten Elemente des iterierbaren Objekts benötigen, z Beispiel) und sollte vermieden werden
Vlad
quelle
3
Schön, aber warum nicht das Sammlungsmodul verwenden, wie in stackoverflow.com/questions/1952464/… vorgeschlagen ? Scheint mir ausdrucksvoller zu sein.
Dave Abrahams
1
Es ist kürzer (und erfordert keine zusätzlichen Importe), ohne an Klarheit zu verlieren: Eine "enthält" -Methode ist eine natürliche Methode, um zu überprüfen, ob es sich bei etwas um eine Sammlung von Objekten handelt.
Vlad
46
Nur weil etwas etwas enthalten kann, heißt das nicht unbedingt, dass es iterierbar ist. Ein Benutzer kann beispielsweise überprüfen, ob sich ein Punkt in einem 3D-Würfel befindet. Wie würden Sie dieses Objekt jedoch durchlaufen?
Casey Kuball
13
Das ist falsch. Ein iterable selbst unterstützt keine enthält , zumindest mit Python 3.4.
Peter Shinners
15

Sie könnten dies versuchen:

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

Wenn wir einen Generator erstellen können, der darüber iteriert (aber niemals den Generator verwendet, damit er keinen Platz beansprucht), ist er iterierbar. Scheint wie eine "duh" Sache. Warum müssen Sie feststellen, ob eine Variable überhaupt iterierbar ist?

Chris Lutz
quelle
Was ist mit iterable(itertools.repeat(0))? :)
Badp
5
@badp, das (x for x in a)erstellt nur einen Generator, es macht keine Iteration auf einem.
catchmeifyoutry
5
Ist das Ausprobieren (x for x in a)genau gleichbedeutend mit dem Ausprobieren iterator = iter(a)? Oder gibt es einige Fälle, in denen die beiden unterschiedlich sind?
Max
Ist das nicht for _ in a: breakeinfacher? Ist es langsamer?
Mr_and_Mrs_D
2
@Mr_and_Mrs_D Das ist schlecht, wenn das getestete Objekt ein Iterator ist, der anschließend wiederholt wird (es wird 1 Element fehlen, da seine Position nicht zurückgesetzt werden kann). Das Erstellen von Garbage Generatoren iteriert nicht über das Objekt, da sie nicht wiederholt werden. obwohl ich nicht sicher bin, ob es einen TypeError zu 100% auslöst, wenn es nicht iterierbar ist.
Tcll
13

Ich habe hier eine schöne Lösung gefunden :

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

Nach dem Python 2-Glossar sind iterable

alle Sequenztypen (z list , str, und tuple) und einige nicht-Sequenztypen wie dictund fileund Objekte aller Klassen , die Sie mit ein definieren __iter__()oder __getitem__()Verfahren. Iterables können in einer for-Schleife und an vielen anderen Stellen verwendet werden, an denen eine Sequenz benötigt wird (zip (), map (), ...). Wenn ein iterierbares Objekt als Argument an die integrierte Funktion iter () übergeben wird, wird ein Iterator für das Objekt zurückgegeben.

Angesichts des allgemeinen Codierungsstils für Python, der auf der Tatsache basiert, dass es „einfacher ist, um Vergebung zu bitten als um Erlaubnis“, besteht die allgemeine Erwartung darin, diese zu verwenden

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

Wenn Sie dies jedoch explizit überprüfen müssen, können Sie nach einem iterierbaren Test suchen hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). Sie müssen nach beiden suchen, da strs keine __iter__Methode haben (zumindest nicht in Python 2, in Python 3) und weil generatorObjekte keine __getitem__Methode haben.

Anaphory
quelle
8

In meinen Skripten finde ich es oft bequem, eine iterableFunktion zu definieren . (Enthält jetzt die von Alfe vorgeschlagene Vereinfachung):

import collections

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

So können Sie testen, ob ein Objekt in der gut lesbaren Form iterierbar ist

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

wie Sie es mit der callableFunktion tun würden

BEARBEITEN: Wenn Sie numpy installiert haben, können Sie einfach tun: von numpy import iterable, was einfach so ähnlich ist

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

Wenn Sie kein Numpy haben, können Sie einfach diesen oder den obigen Code implementieren.

Fmonegaglia
quelle
3
Wann immer Sie etwas mögen if x: return True else: return False(mit xBooleschen Werten), können Sie dies als schreiben return x. In deinem Fall return isinstance(…)ohne if.
Alfe
Da Sie anerkennen, dass die Lösung von Alfe besser ist, warum haben Sie Ihre Antwort nicht bearbeitet, um das einfach zu sagen? Stattdessen haben Sie jetzt BEIDE Versionen in Ihrer Antwort. Unnötige Ausführlichkeit. Senden einer Bearbeitung, um dies zu beheben.
ToolmakerSteve
2
Sie sollten "TypeError" in der Zeile "außer: return False" abfangen. Alles zu fangen ist ein schlechtes Muster.
Mariusz Jamro
Wisse das. Ich habe diesen Code aus der NumPy-Bibliothek übersetzt, die die generische Ausnahme verwendet.
Fmonegaglia
Nur weil ein Code aus NumPy stammt, heißt das nicht, dass er gut ist ... Muster oder nicht. Das einzige Mal, wenn alles abgefangen werden sollte, ist, wenn Sie explizit Fehler in Ihrem Programm behandeln.
Tcll
5

hat eine eingebaute Funktion wie diese:

from pandas.util.testing import isiterable
Sören
quelle
Dies sieht jedoch nur so aus, ob es __iter__Sequenzen und ähnliches gibt und nicht.
Lesen Sie den
4

Es ist mir immer entgangen, warum Python hat, callable(obj) -> boolaber nicht iterable(obj) -> bool...
sicherlich ist es einfacher zu tunhasattr(obj,'__call__') selbst wenn es langsamer ist.

Da fast jede andere Antwort die Verwendung von try/ empfiehlt except TypeError, wobei das Testen auf Ausnahmen in einer Sprache im Allgemeinen als schlechte Praxis angesehen wird, ist hier eine Implementierung von, die iterable(obj) -> boolich immer häufiger mag und verwende:

Um Python 2 willen werde ich ein Lambda nur für diesen zusätzlichen Leistungsschub verwenden ...
(In Python 3 spielt es keine Rolle, was Sie zum Definieren der Funktion verwenden, es defhat ungefähr die gleiche Geschwindigkeit wie lambda)

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

Beachten Sie, dass diese Funktion für Objekte mit schneller ausgeführt wird, __iter__da sie nicht getestet wird__getitem__ .

Die meisten iterierbaren Objekte sollten sich darauf verlassen, __iter__wo Sonderfallobjekte zurückgreifen __getitem__, obwohl beides erforderlich ist, damit ein Objekt iterierbar ist.
(und da dies Standard ist, betrifft es auch C-Objekte)

Tcll
quelle
Er stellt keinen Arbeitscode zur Verfügung, geschweige denn über die Python-Leistung ... obwohl diese Antwort wirklich nur der Einfachheit halber war, wie ich es hier schon oft gesehen habe.
Tcll
3
def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

Dies sagt Ja zu allen Arten von iterierbaren Objekten, aber es sagt Nein zu Zeichenfolgen in Python 2 . (Das ist es, was ich zum Beispiel möchte, wenn eine rekursive Funktion einen String oder einen Container mit Strings aufnehmen kann. In dieser Situation kann das Bitten um Vergebung zu Obfuscode führen, und es ist besser, zuerst um Erlaubnis zu bitten.)

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)

Viele andere Strategien hier sagen Ja zu Strings. Verwenden Sie sie, wenn Sie dies möchten.

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

Hinweis: is_iterable () sagt Ja zu Zeichenfolgen vom Typ bytesund bytearray.

  • bytesObjekte in Python 3 sind iterierbar True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))In Python 2 gibt es keinen solchen Typ.
  • bytearray Objekte in Python 2 und 3 sind iterierbar True == is_iterable(bytearray(b"abc"))

Der OP- hasattr(x, '__iter__')Ansatz sagt Ja zu Zeichenfolgen in Python 3 und Nein zu Python 2 (egal ob ''oder b''oder u''). Vielen Dank an @LuisMasuelli, dass Sie bemerkt haben, dass Sie auch einen Buggy im Stich lassen werden __iter__.

Bob Stein
quelle
2

Der einfachste Weg, unter Wahrung des Pythons Ente eingeben , ist der Fehler zu fangen (Python weiß genau , was tut es von einem Objekt erwartet einen Iterator zu werden):

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

Anmerkungen :

  1. Es ist unerheblich, ob das Objekt nicht iterierbar ist oder ein Buggy __iter__implementiert wurde, wenn der Ausnahmetyp derselbe ist: Sie können das Objekt ohnehin nicht iterieren.
  2. Ich glaube, ich verstehe Ihr Anliegen: Wie callablebesteht eine Prüfung, ob ich mich auch auf die Eingabe von Enten verlassen kann, um ein AttributeErrorWenn auszulösen, das __call__für mein Objekt nicht definiert ist, aber dies ist bei iterierbaren Prüfungen nicht der Fall?

    Ich kenne die Antwort nicht, aber Sie können entweder die von mir (und anderen Benutzern) angegebene Funktion implementieren oder einfach die Ausnahme in Ihrem Code abfangen (Ihre Implementierung in diesem Teil entspricht der von mir geschriebenen Funktion - stellen Sie einfach sicher, dass Sie die isolieren Iterator-Erstellung aus dem Rest des Codes, damit Sie die Ausnahme erfassen und von einer anderen unterscheiden können TypeError.

Luis Masuelli
quelle
1

Die Funktion isiterableim folgenden Code wird zurückgegeben, Truewenn das Objekt iterierbar ist. Wenn es nicht iterierbar ist, wird zurückgegebenFalse

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

Beispiel

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
Nomade
quelle
2
so viele detaillierte Antworten oben mit vielen positiven Stimmen und Sie werfen eine ungeklärte Antwort ein ... meh
Nrzonline
Bitte geben Sie keinen bloßen Code ein. Fügen Sie auch eine Erklärung hinzu, was dies tut.
Jonathan Mee
1

Anstatt nach dem __iter__Attribut zu __len__suchen , können Sie auch nach dem Attribut suchen, das von jedem iterierbaren Python implementiert wird, einschließlich Zeichenfolgen.

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

Nicht iterierbare Objekte würden dies aus offensichtlichen Gründen nicht implementieren. Es werden jedoch weder benutzerdefinierte Iterables abgefangen, die es nicht implementieren, noch Generatorausdrücke, die iterdamit umgehen können. Dies kann jedoch in einer Zeile erfolgen, und das Hinzufügen einer einfachen orAusdrucksprüfung für Generatoren würde dieses Problem beheben. (Beachten Sie, dass das Schreiben type(my_generator_expression) == generatoreinen NameErrorauslösen würde . Lesen Sie stattdessen diese Antwort.)

Sie können GeneratorType aus folgenden Typen verwenden:

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

--- akzeptierte Antwort von utdemir

(Dies macht es nützlich, um zu überprüfen, ob Sie lendas Objekt aufrufen können .)

DarthCadeus
quelle
Leider verwenden nicht alle iterierbaren Objekte __len__... In diesem Fall wird normalerweise die Entfernung zwischen zwei Objekten nicht ordnungsgemäß berechnet. wo obj.dist()könnte leicht ersetzt werden.
Tcll
Ja. Die meisten benutzerdefinierten Iterables implementieren Iter und Getitem, jedoch nicht len. Integrierte Typen tun dies jedoch, und wenn Sie überprüfen möchten, ob Sie die len-Funktion darauf aufrufen können, ist die Überprüfung auf len sicherer. Aber Sie haben Recht.
DarthCadeus
0

Nicht wirklich "korrekt" , kann aber als schnelle Überprüfung der gängigsten Typen wie Zeichenfolgen, Tupel, Floats usw. dienen.

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

Ein bisschen spät zur Party, aber ich habe mir diese Frage gestellt und gesehen, dass mir dann eine Antwort einfällt. Ich weiß nicht, ob jemand dies bereits gepostet hat. Aber im Wesentlichen habe ich festgestellt, dass alle iterierbaren Typen __getitem __ () in ihrem Diktat haben. Auf diese Weise können Sie überprüfen, ob ein Objekt iterierbar ist, ohne es zu versuchen. (Wortspiel beabsichtigt)

def is_attr(arg):
    return '__getitem__' in dir(arg)
lakam99
quelle
Dies ist leider unzuverlässig. Beispiel
Timgeb
1
Set-Objekte sind ein weiteres Gegenbeispiel.
Raymond Hettinger