Frage Wie ermittle ich in Python, ob ein Objekt iterierbar ist?


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 dumm das ist.


802
2017-12-23 12:13


Ursprung


Antworten:


  1. Überprüfung auf __iter__ arbeitet an Sequenztypen, aber es würde z.B. Saiten in Python 2. Ich würde gerne auch die richtige Antwort wissen, bis dahin, hier ist eine Möglichkeit (die auch für Streicher funktionieren würde):

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

    Das iter integrierte Schecks für die __iter__ Methode oder im Falle von Strings die __getitem__ Methode.

  2. Ein weiterer allgemeiner pythonischer Ansatz besteht darin, ein iterables anzunehmen und dann fehlerfrei zu versagen, wenn es für das gegebene Objekt nicht funktioniert. Das Python-Glossar:

    Pythonic-Programmierstil, der den Typ eines Objekts durch Überprüfung seiner Methode oder Attributsignatur und nicht durch eine explizite Beziehung zu einem Typobjekt bestimmt ("Wenn es wie ein a aussieht Ente und Quacksalber wie ein Ente, es muss ein sein Ente") Durch die Betonung von Schnittstellen anstelle von spezifischen Typen verbessert gut konzipierter Code seine Flexibilität, indem er eine polymorphe Substitution ermöglicht. Bei der Duck-Typisierung werden Tests mit type () oder isinstance () vermieden. Stattdessen verwendet es typischerweise den EAFP-Programmierstil (Einfacher zu Vergeben als Erlaubnis).

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
    
  3. Das collections Das Modul stellt einige abstrakte Basisklassen zur Verfügung, mit denen Klassen oder Instanzen gefragt werden können, wenn sie bestimmte Funktionen bereitstellen, zum Beispiel:

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

    Dies prüft jedoch nicht auf Klassen, die durchgängig sind __getitem__.


644
2017-12-23 12:16



Ente tippen

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

# for obj in iterator:
#     pass

Typprüfung

Benutze die Abstrakte Basisklassen. Sie benötigen mindestens Python 2.6 und arbeiten nur für neue Klassen.

import collections

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

Jedoch, iter() ist ein bisschen zuverlässiger als beschrieben durch die Dokumentation:

Überprüfung isinstance(obj, Iterable) erkennt Klassen, die sind   registriert als Iterable oder die haben ein __iter__() Methode, aber   Es erkennt keine Klassen, die mit dem iterieren __getitem__()   Methode. Der einzige zuverlässige Weg, um festzustellen, ob ein Objekt   iterabel ist es anzurufen iter(obj).


491
2017-12-23 12:53



Ich würde gerne ein bisschen mehr Licht auf das Zusammenspiel von iter, __iter__ und __getitem__ und was passiert hinter den Vorhängen. 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 dann mit einer kurzen Erinnerung daran, was passiert, wenn Sie einen beschäftigen for Schleife in Python, gefolgt von einer Diskussion, um die Fakten zu veranschaulichen.

Fakten

  1. Sie können einen Iterator von jedem Objekt erhalten o durch anruf iter(o) wenn mindestens eine der folgenden Bedingungen zutrifft:

    ein) o hat ein __iter__ Methode, die ein Iterator-Objekt zurückgibt. Ein Iterator ist ein beliebiges Objekt mit einem __iter__ und ein __next__ (Python 2: next) Methode.

    b) o hat ein __getitem__ Methode.

  2. Auf eine Instanz von Iterable oder Sequenceoder nach dem suchen Attribut __iter__ ist nicht genug.

  3. Wenn ein Objekt o implementiert nur __getitem__, aber nicht __iter__, iter(o)wird konstruieren ein Iterator, der versucht, Elemente von zu holen o durch Ganzzahl-Index, beginnend bei Index 0. Der Iterator wird alle fangen IndexError (aber keine anderen Fehler), die ausgelöst und dann erhöht wird StopIteration selbst.

  4. Im allgemeinsten Sinne gibt es keine Möglichkeit zu überprüfen, ob der Iterator von zurückgegeben wurde iter ist gesund, außer es auszuprobieren.

  5. Wenn ein Objekt o implementiert __iter__, das iter Funktion wird sicherstellen dass das Objekt von zurückgegeben wird __iter__ ist ein Iterator. Es gibt keine Überprüfung der Gesundheit wenn ein Objekt nur implementiert __getitem__.

  6. __iter__ Gewinnt. Wenn ein Objekt o implementiert beides __iter__ und __getitem__, iter(o) werde anrufen __iter__.

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

for Schleifen

Um mitzukommen, müssen Sie verstehen, was passiert, wenn Sie einen Mitarbeiter einstellen for Schleife in Python. Zögern Sie nicht, direkt zum nächsten Abschnitt zu springen, wenn Sie es bereits wissen.

Wenn Sie verwenden for item in o für ein iterierbares Objekt o, Python ruft auf iter(o) und erwartet ein Iterator-Objekt als Rückgabewert. Ein Iterator ist ein beliebiges Objekt, das a implementiert __next__ (oder next in Python 2) Methode und ein __iter__ Methode.

Durch Vereinbarung, die __iter__ Methode eines Iterators sollte das Objekt selbst zurückgeben return self). Python ruft dann auf next auf dem Iterator bis StopIteration ist erhöht. 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 ein 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: Ermitteln eines Iterators und unzuverlässige Prüfungen

Betrachten Sie die folgende Klasse:

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

Berufung iter mit einer Instanz von BasicIterable wird einen Iterator ohne Probleme zurückgeben, weil BasicIterable implementiert __getitem__.

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

Es ist jedoch wichtig, dies zu beachten b hat nicht die __iter__ Attribut und wird nicht als Instanz von betrachtet Iterable oder Sequence:

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

Deshalb Fließende Python von Luciano Ramalho empfiehlt anzurufen iter und Umgang mit dem Potenzial TypeError als der genaueste Weg, um zu überprüfen, ob ein Objekt iterierbar ist. Zitat direkt aus dem Buch:

Ab Python 3.4 der genaueste Weg, um zu überprüfen, ob ein Objekt vorhanden ist x iterabel ist es anzurufen iter(x) und handle a TypeError Ausnahme, wenn es nicht ist. Dies ist genauer als die Verwendung isinstance(x, abc.Iterable) , weil iter(x) berücksichtigt auch das Vermächtnis __getitem__ Methode, während die Iterable ABC nicht.

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

Iterieren über eine Instanz von BasicIterable funktioniert wie erwartet: Python Konstruiert einen Iterator, der versucht, Elemente nach Index abzurufen, beginnend bei Null, bis ein IndexError ist erhöht. Das Demo-Objekt ist __getitem__ Methode gibt einfach die item das wurde als Argument geliefert __getitem__(self, item) durch den Iterator von zurückgegeben 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 StopIteration wenn es das nächste Element nicht zurückgeben kann und das IndexError welches für angehoben wird item == 3 wird intern behandelt. Dies ist der Grund, warum man über einen BasicIterable mit einem for Schleife funktioniert wie erwartet:

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

Hier ist ein weiteres Beispiel, um das Konzept des Iterators zurückzubringen iterversucht, über Index auf Elemente zuzugreifen. WrappedDict erbt nicht von dict, was bedeutet, dass Instanzen keine haben __iter__ Methode.

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 Anrufe an __getitem__ sind delegiert an dict.__getitem__ für die die eckige Klammernnotation einfach eine Kurzschrift 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 den Punkten 4 und 5: iter Sucht beim Aufruf nach einem Iterator __iter__:

Wann iter(o) ist für ein Objekt aufgerufen o, iter wird sicherstellen, dass der Rückgabewert von __iter__Wenn die Methode vorhanden ist, handelt es sich um einen Iterator. Dies bedeutet, dass das zurückgegebene Objekt muss umsetzen __next__ (oder next in Python 2) und __iter__. iter kann nur für Objekte, die nur die Integrität überprüfen zu Verfügung stellen __getitem__, weil es keine Möglichkeit gibt, zu überprüfen, ob die Objekte des Objekts über den Ganzzahlindex zugänglich sind.

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

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

Beachten Sie, dass Sie einen Iterator aus FailIterIterable Instanzen schlägt sofort fehl, während ein Iterator von FailGetItemIterable ist erfolgreich, wird beim ersten Aufruf jedoch eine Ausnahme auslösen __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 einfach. Wenn ein Objekt implementiert wird __iter__ und __getitem__, iter werde anrufen __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 mögen list implementieren ein __iter__ Methode wann __getitem__ wäre ausreichend.

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 obigen Klasse, die Aufrufe an delegiert __getitem__ zu list.__getitem__ (mit der quadratischen Klammer Notation), wird 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 wie folgt:

  1. Wenn Sie es implementieren __iter__, Instanzen werden als iterierbar betrachtet, und isinstance(o, collections.Iterable) wird zurückkehren True.
  2. Wenn das Objekt von zurückgegeben wird __iter__ ist kein Iterator, iter wird sofort scheitern und eine erhöhen TypeError.
  3. Die besondere Handhabung von __getitem__ besteht aus Gründen der Rückwärtskompatibilität. Von Fluent Python erneut zitieren:

Deshalb ist jede Python-Sequenz iterabel: Sie implementieren alle __getitem__ . Eigentlich,   die Standardsequenzen implementieren auch __iter__und deine sollten auch, weil die   spezielle Handhabung von __getitem__ besteht aus Gründen der Rückwärtskompatibilität und kann sein   in der Zukunft gegangen (obwohl es nicht veraltet ist, wie ich dies schreibe).


55
2018-04-04 16:04



Dies ist nicht ausreichend: das Objekt, das zurückgegeben wird __iter__ muss das Iterationsprotokoll implementieren (d. h. next Methode). Siehe den entsprechenden Abschnitt in der Dokumentation.

In Python ist es eine gute Übung, "zu versuchen" und nicht zu "checken".


28
2017-12-23 12:17



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

Führen Sie keine Überprüfungen durch, um zu sehen wenn deine Ente wirklich eine Ente ist um zu sehen, ob es iterabel ist oder nicht, behandeln Sie es als ob es war und beschweren Sie sich, wenn es nicht war.


18
2017-12-23 12:21



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

hasattr(obj, '__contains__')

was im Grunde prüft ob das Objekt das implementiert in Operator.

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

  • es ist ein Ausdruck (arbeitet als ein Lambdaim Gegensatz zu den versuch ... außer Variante)
  • Es ist (sollte) von allen Iterablen implementiert werden, einschließlich Saiten (im Gegensatz zu __iter__)
  • funktioniert auf jedem Python> = 2.5

Anmerkungen:

  • Die Python-Philosophie "um Vergebung bitten, nicht um Erlaubnis bitten" funktioniert nicht gut, wenn z. In einer Liste haben Sie sowohl iterierbare als auch nicht-iterierbare Elemente, und Sie müssen jedes Element je nach seinem Typ unterschiedlich behandeln (Behandeln von Iterablen auf try und nicht-iterierbaren auf außer) würde Arbeit, aber es würde hässlich und irreführend aussehen)
  • Lösungen für dieses Problem, die versuchen, tatsächlich über das Objekt zu iterieren (z. B. [x für x in obj]), um zu überprüfen, ob es iterierbar ist, können signifikante Leistungseinbußen für große iterierbare Werte verursachen (besonders wenn Sie nur die ersten Elemente des iterablen, z Beispiel) und sollte vermieden werden

16
2017-11-09 16:40



In Python <= 2.5 kann und sollte nicht - iterable war eine "informelle" Schnittstelle.

Aber seit Python 2.6 und 3.0 können Sie die neue ABC-Infrastruktur (abstrakte Basisklasse) zusammen mit einigen eingebauten 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)

Nun, ob dies wünschenswert ist oder tatsächlich funktioniert, ist nur eine Frage der Konventionen. Wie du sehen kannst, du kann Registrieren Sie ein nicht iterierbares Objekt als Iterable - und es wird zur Laufzeit eine Ausnahme ausgelöst. Daher erhält isinstance eine "neue" Bedeutung - es prüft nur die Kompatibilität mit dem "deklarierten" Typ, was in Python ein guter Weg ist.

Auf der anderen Seite, wenn Ihr Objekt die benötigte Schnittstelle nicht erfüllt, was werden Sie tun? 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 dem entspricht, was Sie erwarten, werfen Sie einfach einen TypeError, aber wenn das richtige ABC registriert wurde, ist Ihre Prüfung nicht sinnvoll. Im Gegenteil, wenn die __iter__ Methode ist verfügbar Python erkennt das Objekt dieser Klasse automatisch als Iterable.

Wenn Sie also nur ein iterables erwarten, iterieren Sie darüber und vergessen Sie es. Auf der anderen Seite, wenn Sie verschiedene Dinge abhängig vom Eingabetyp machen müssen, finden Sie die ABC-Infrastruktur ziemlich nützlich.


14
2017-12-23 13:35



Ich habe eine nette Lösung gefunden Hier:

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

11
2017-12-23 13:13