W Pythonie, jak ustalić, czy obiekt jest iterowalny?

1083

Czy istnieje jakaś metoda isiterable? Jedynym rozwiązaniem, które do tej pory znalazłem, jest połączenie

hasattr(myObj, '__iter__')

Ale nie jestem pewien, czy jest to głupie.

willem
źródło
18
__getitem__wystarcza również, aby obiekt mógł być iterowalny
Kos
4
FWIW: iter(myObj)jeśli się uda isinstance(myObj, dict), więc jeśli spojrzysz myObjna sekwencję dicts lub pojedynczą dict, odniesiesz sukces w obu przypadkach. Subtelność, która jest ważna, jeśli chcesz wiedzieć, co jest sekwencją, a co nie. (w Python 2)
Ben Mosher
7
__getitem__wystarcza również, aby obiekt był iterowalny ... jeśli zaczyna się od zera indeksu .
Carlos A. Gómez

Odpowiedzi:

26

Ostatnio dość często studiowałem ten problem. Na tej podstawie stwierdzam, że obecnie jest to najlepsze podejście:

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

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

Powyższe zalecono już wcześniej, ale ogólny konsensus jest taki, że stosowanie iter()byłoby lepsze:

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

Użyliśmy również iter()w naszym kodzie do tego celu, ale ostatnio zacząłem coraz bardziej denerwować się obiektami, które tylko __getitem__uważano za iterowalne. Istnieją ważne powody, by mieć obiekt, __getitem__którego nie da się iterować, a powyższy kod nie działa dobrze. Jako przykład z życia możemy użyć Fakera . Powyższy kod zgłasza, że ​​jest iterowalny, ale w rzeczywistości próba iteracji powoduje AttributeError(testowany z 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'

Gdybyśmy tego użyli insinstance(), przypadkowo nie uznalibyśmy instancji Fakera (ani żadnych innych obiektów posiadających tylko __getitem__) za iterowalne:

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

Wcześniejsze odpowiedzi skomentowały, że używanie iter()jest bezpieczniejsze, ponieważ opierał się na starym sposobie implementacji iteracji w Pythonie __getitem__i isinstance()podejście to nie wykrywa. Mogło to być prawdą w przypadku starych wersji Pythona, ale w oparciu o moje dość wyczerpujące testy isinstance()działają obecnie świetnie. Jedyny przypadek, w którym isinstance()nie działał, ale działał, iter()dotyczył UserDictużywania Pythona 2. Jeśli jest to istotne, można skorzystać isinstance(item, (Iterable, UserDict))z tej opcji.

Pekka Klärck
źródło
1
typing.DictJest również uważany za iterowalny, iter(Dict)ale list(Dict)kończy się błędem TypeError: Parameters to generic types must be types. Got 0.. Zgodnie z oczekiwaniami isinstance(Dict, Iterable)zwraca false.
Pekka Klärck
1
Doszedłem do tego samego wniosku, ale z różnych powodów. Użycie iterspowodowało, że część naszego kodu, który używał „buforowania wstępnego”, niepotrzebnie zwalniała. Jeśli __iter__kod jest wolny, zadzwoni iter... za każdym razem, gdy chcesz tylko sprawdzić, czy coś da się powtórzyć.
thorwhalen
842
  1. Sprawdzanie, czy __iter__działa na typach sekwencji, ale nie powiedzie się np. Na łańcuchach w Pythonie 2 . Chciałbym również znać właściwą odpowiedź, do tego czasu jest jedna możliwość (która również działałaby na ciągach):

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

    Te iterwbudowane kontrole na __iter__metodzie lub w przypadku strun do __getitem__metody.

  2. Innym ogólnym podejściem pythonowym jest założenie, że jest iterowalny, a następnie kończy się niepowodzeniem, jeśli nie działa na danym obiekcie. Glosariusz Pythona:

    Pythoniczny styl programowania, który określa typ obiektu poprzez sprawdzenie jego metody lub podpisu atrybutu, a nie poprzez wyraźne powiązanie z jakimś obiektem typu („Jeśli wygląda jak kaczka i kwacze jak kaczka , musi to być kaczka .”) Poprzez podkreślenie interfejsów zamiast określonych typów dobrze zaprojektowany kod poprawia jego elastyczność, umożliwiając podstawienie polimorficzne. Wpisywanie kaczych pozwala uniknąć testów z użyciem type () lub isinstance (). Zamiast tego zazwyczaj wykorzystuje styl programowania EAFP (łatwiej prosić o przebaczenie niż pozwolenie).

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
    
  3. collectionsModuł dostarcza pewnych abstrakcyjnych klas bazowych, które pozwalają zadać klas lub instancji, jeżeli zapewniają one szczególną funkcję, na przykład:

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

    Nie sprawdza to jednak klas, które można iterować __getitem__.

miku
źródło
34
[e for e in my_object]może zgłosić wyjątek z innych powodów, tj. my_objectjest niezdefiniowany lub możliwe błędy we my_objectwdrażaniu.
Nick Dandoulakis,
37
Ciąg jest sekwencją ( isinstance('', Sequence) == True) i dowolną sekwencją jest iterowalna ( isinstance('', Iterable)). Chociaż hasattr('', '__iter__') == Falsemoże to być mylące.
jfs
82
Jeśli my_objectjest bardzo duży (powiedzmy, nieskończony jak itertools.count()), zrozumienie listy zajmie dużo czasu / pamięci. Lepiej jest stworzyć generator, który nigdy nie będzie próbował zbudować listy (potencjalnie nieskończonej).
Chris Lutz
14
Co jeśli jakiś obiekt rzuca błąd typu TypeError spowodowany również z innego powodu (błędy itp.)? Jak możemy to rozpoznać po „Błędzie typu iterowalnego”?
Shaung
54
Zauważ, że w Pythonie 3: hasattr(u"hello", '__iter__')zwracaTrue
Carlos
572

Pisanie kaczek

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

# for obj in iterator:
#     pass

Sprawdzanie typu

Użyj abstrakcyjnych klas podstawowych . Potrzebują co najmniej Pythona 2.6 i działają tylko w przypadku klas nowego stylu.

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

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

Jednakże, iter()jest nieco bardziej wiarygodne, jak to opisano w dokumentacji :

Sprawdzanie isinstance(obj, Iterable)wykrywa klasy, które są zarejestrowane jako Iterable lub mają __iter__()metodę, ale nie wykrywa klas, które iterują z tą __getitem__() metodą. Jedynym niezawodnym sposobem ustalenia, czy obiekt jest iterowalny, jest wywołanie iter(obj).

Georg Schölly
źródło
18
Z „Fluent Python” Luciano Ramalho: Począwszy od Python 3.4, najdokładniejszym sposobem sprawdzenia, czy obiekt x jest iterowalny, jest wywołanie iter (x) i obsłużenie wyjątku TypeError, jeśli nie jest. Jest to dokładniejsze niż użycie isinstance (x, abc.Iterable), ponieważ iter (x) uwzględnia również starszą metodę getitem , podczas gdy Iterable ABC nie.
RdB
Jeśli myślisz „och, po prostu isinstance(x, (collections.Iterable, collections.Sequence))zamiast iter(x)”, pamiętaj, że nadal nie wykryje iterowalnego obiektu, który implementuje tylko, __getitem__ale nie __len__. Użyj iter(x)i złap wyjątek.
Dale
Twoja druga odpowiedź nie działa. Jeśli to zrobię w PyUNO iter(slide1), wszystko pójdzie dobrze, ale isinstance(slide1, Iterable)rzuty TypeError: issubclass() arg 1 must be a class.
Hi-Angel,
@ Hi-Angel brzmi jak błąd w PyUNOZauważ, że issubclass()zamiast tego wyświetlany jest komunikat o błędzie isinstance().
Georg Schölly,
2
Wywołanie iter () nad obiektem może być kosztowną operacją (patrz DataLoader w Pytorch, który uruchamia / odradza wiele procesów na iter ()).
szali
125

Chciałbym rzucić nieco więcej światła na grę iter, __iter__a __getitem__i co dzieje się za kurtyną. Uzbrojony w tę wiedzę będziesz w stanie zrozumieć, dlaczego najlepiej możesz to zrobić

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

Najpierw wymienię fakty, a następnie szybko przypomnę, co się stanie, gdy zastosujesz forpętlę w pythonie, a następnie przeprowadzimy dyskusję ilustrującą fakty.

Fakty

  1. Możesz pobrać iterator z dowolnego obiektu o, wywołując, iter(o)jeśli spełniony jest przynajmniej jeden z następujących warunków:

    a) oma __iter__metodę, która zwraca obiekt iteratora. Iteratorem jest dowolny obiekt z metodą __iter__i __next__(Python 2 next:).

    b) oma __getitem__metodę.

  2. Sprawdzanie instancji Iterablelub Sequencelub kontroli dla atrybutu __iter__nie jest wystarczające.

  3. Jeśli obiekt oimplementuje tylko __getitem__, ale nie __iter__, iter(o)skonstruuje iterator, który spróbuje pobrać elementy z oindeksu liczb całkowitych, zaczynając od indeksu 0. Iterator wyłapie wszystkie IndexError(ale żadnych innych błędów), które zostaną podniesione, a następnie StopIterationsam się podniesie .

  4. W najogólniejszym sensie nie ma sposobu, aby sprawdzić, czy powracający iterator iterjest zdrowy, poza wypróbowaniem go.

  5. Jeśli obiekt zostanie ozaimplementowany __iter__, iterfunkcja upewni się, że zwracany przez niego obiekt __iter__jest iteratorem. Nie ma kontroli poprawności, jeśli obiekt tylko implementuje __getitem__.

  6. __iter__wygrywa Jeśli obiekt oimplementuje oba __iter__i __getitem__, iter(o)wywoła __iter__.

  7. Jeśli chcesz, aby własne obiekty były iterowalne, zawsze implementuj __iter__metodę.

for pętle

Aby kontynuować, musisz zrozumieć, co się dzieje, gdy zastosujesz forpętlę w Pythonie. Jeśli już wiesz, możesz przejść do następnej sekcji.

Kiedy używasz for item in odla jakiegoś iterowalnego obiektu o, Python wywołuje iter(o)i oczekuje obiektu iteratora jako wartości zwracanej. Iterator to dowolny obiekt, który implementuje metodę __next__(lub nextw Pythonie 2) i __iter__metodę.

Zgodnie z konwencją __iter__metoda iteratora powinna zwrócić sam obiekt (tj return self.). Python następnie wywołuje nextiterator, aż StopIterationzostanie podniesiony. Wszystko to dzieje się niejawnie, ale poniższa demonstracja pokazuje:

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

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

Dyskusja i ilustracje

W punktach 1 i 2: uzyskanie iteratora i nierzetelne kontrole

Rozważ następującą klasę:

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

Wywołanie iterz instancją BasicIterablezwróci iterator bez żadnych problemów, ponieważ BasicIterableimplementuje __getitem__.

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

Należy jednak zauważyć, że bnie ma tego __iter__atrybutu i nie jest uważany za instancję Iterablelub Sequence:

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

Właśnie dlatego Fluent Python autorstwa Luciano Ramalho zaleca wywoływanie iteri zarządzanie potencjałem TypeErrorjako najdokładniejszy sposób sprawdzenia, czy obiekt jest iterowalny. Cytując bezpośrednio z książki:

Od wersji Python 3.4 najdokładniejszym sposobem sprawdzenia, czy obiekt xjest iterowalny, jest wywołanie iter(x)i obsłużenie TypeErrorwyjątku, jeśli tak nie jest. Jest to dokładniejsze niż używanie isinstance(x, abc.Iterable), ponieważ iter(x)uwzględnia również starszą __getitem__metodę, podczas gdy IterableABC nie.

W punkcie 3: Iterowanie nad obiektami, które tylko zapewniają __getitem__, ale nie zapewniają__iter__

Iteracja po instancji BasicIterabledziała zgodnie z oczekiwaniami: Python konstruuje iterator, który próbuje pobierać elementy według indeksu, zaczynając od zera, aż do IndexErrorpodniesienia wartości. Metoda obiektu demonstracyjnego __getitem__zwraca po prostu to, itemco podano jako argument __getitem__(self, item)zwracany przez iterator 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

Zauważ, że iterator podnosi się, StopIterationgdy nie może zwrócić następnego elementu, a ten, dla IndexErrorktórego podniesiono, item == 3jest obsługiwany wewnętrznie. Oto dlaczego zapętlenie BasicIterablez forpętlą działa zgodnie z oczekiwaniami:

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

Oto kolejny przykład, który pokazuje, w jaki sposób iterator wrócił przez iterpróby uzyskania dostępu do elementów według indeksu. WrappedDictnie dziedziczy po dict, co oznacza, że ​​instancje nie będą miały __iter__metody.

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

Zauważ, że wywołania do __getitem__są delegowane, dict.__getitem__dla których notacja w nawiasach kwadratowych jest po prostu skrótem.

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

W punktach 4 i 5: itersprawdza iterator, gdy wywołuje__iter__ :

Kiedy iter(o)zostanie wywołany dla obiektu o, iterupewni się, że zwracana wartość __iter__, jeśli metoda jest obecna, jest iteratorem. Oznacza to, że zwracany obiekt musi implementować __next__(lub nextw Pythonie 2) i __iter__. iternie może wykonać żadnych kontroli poczytalności dla obiektów, które tylko zapewniają __getitem__, ponieważ nie ma możliwości sprawdzenia, czy elementy obiektu są dostępne przez indeks liczb całkowitych.

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

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

Zauważ, że konstruowanie iteratora z FailIterIterableinstancji kończy się niepowodzeniem natychmiast, podczas gdy konstruowanie iteratora z FailGetItemIterablepowodzeniem, ale generuje wyjątek przy pierwszym wywołaniu do __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

W punkcie 6: __iter__wygrywa

Ten jest prosty. Jeśli obiekt zaimplementuje __iter__i __getitem__, iterzadzwoni __iter__. Rozważ następującą klasę

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

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

oraz dane wyjściowe podczas zapętlania instancji:

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

W punkcie 7: twoje klasy iterowalne powinny zaimplementować __iter__

Możesz zadać sobie pytanie, dlaczego większość wbudowanych sekwencji, takich jak listimplementacja __iter__metody __getitem__, jest wystarczająca.

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

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

Po tym wszystkim, iteracja przez instancje klasy wyżej, który deleguje do połączenia __getitem__się list.__getitem__(za pomocą notacji nawiasu kwadratowego), będzie działać prawidłowo:

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

Powody, dla których niestandardowe iteratory powinny zostać zaimplementowane, __iter__są następujące:

  1. Jeśli zaimplementujesz __iter__, instancje zostaną uznane za iterowalne i isinstance(o, collections.abc.Iterable)zostaną zwrócone True.
  2. Jeśli obiekt zwracany przez __iter__nie jest iteratorem, iternatychmiast zawiedzie i podniesie wartość TypeError.
  3. Specjalna obsługa __getitem__istnieje ze względu na kompatybilność wsteczną. Cytując ponownie z płynnego Pythona:

Dlatego każda sekwencja Pythona jest iterowalna: wszystkie implementują __getitem__. W rzeczywistości standardowe sekwencje również się implementują __iter__, a twoja też powinna, ponieważ specjalna obsługa __getitem__istnieje ze względu na kompatybilność wsteczną i może zniknąć w przyszłości (chociaż nie jest przestarzała, kiedy to piszę).

timb
źródło
więc jest to bezpieczne, aby zdefiniować predykat is_iterablepowracając Truew trybloku i Falsew except TypeErrorbloku?
alancalvitti
To świetna odpowiedź. Myślę, że podkreśla to nieintuicyjny i niefortunny charakter protokołu getitem. Nigdy nie powinien był zostać dodany.
Neil G
31

To nie wystarcza: obiekt zwracany przez __iter__musi implementować protokół iteracji (tj. nextMetodę). Zobacz odpowiedni rozdział w dokumentacji .

W Pythonie dobrą praktyką jest „spróbuj i zobacz” zamiast „sprawdzania”.

Jldupont
źródło
9
„pisanie kaczek” Wierzę? :)
willem
9
@willem: lub „nie proś o pozwolenie, ale o wybaczenie” ;-)
jldupont
14
@willem Zarówno style „pozwolenie”, jak i „wybaczenie” kwalifikują się jako pisanie kaczek. Jeśli zapytasz, co może zrobić obiekt , a nie czym on jest , jest to pisanie kaczki. Jeśli używasz introspekcji, jest to „pozwolenie”; jeśli tylko spróbujesz to zrobić i zobaczysz, czy to działa, czy nie, to jest to „wybaczenie”.
Mark Reed,
22

W Pythonie <= 2.5 nie możesz i nie powinieneś - iterowalny był „nieformalny” interfejs.

Ale od wersji Python 2.6 i 3.0 możesz wykorzystać nową infrastrukturę ABC (abstrakcyjna klasa bazowa) wraz z niektórymi wbudowanymi ABC, które są dostępne w module kolekcji:

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)

To, czy jest to pożądane, czy faktycznie działa, jest tylko kwestią konwencji. Jak widać, to można zarejestrować bez iterable obiektu jako iterable - i to podniesie wyjątek w czasie wykonywania. Dlatego isinstance nabiera „nowego” znaczenia - po prostu sprawdza zgodność z „zadeklarowanym” typem, co jest dobrym sposobem na przejście do Pythona.

Z drugiej strony, jeśli twój obiekt nie spełnia potrzebnego interfejsu, co zamierzasz zrobić? Weź następujący przykład:

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

Jeśli obiekt nie spełnia oczekiwań, po prostu generujesz błąd typu TypeError, ale jeśli zarejestrowano prawidłowe ABC, czek jest nieużyteczny. Przeciwnie, jeśli __iter__metoda jest dostępna, Python automatycznie rozpozna obiekt tej klasy jako Iterowalny.

Jeśli więc spodziewasz się iteracji, iteruj ją i zapomnij. Z drugiej strony, jeśli musisz robić różne rzeczy w zależności od typu danych wejściowych, infrastruktura ABC może okazać się bardzo przydatna.

Alan Franzoni
źródło
13
nie używaj goły except:w przykładowym kodzie dla początkujących. Promuje złe praktyki.
jfs
JFS: Nie zrobiłbym tego, ale musiałem przejść przez wiele kodów wywołujących wyjątki i nie chciałem wychwycić konkretnego wyjątku ... Myślę, że cel tego kodu jest całkiem jasny.
Alan Franzoni
21
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

Nie sprawdzaj, czy twoja kaczka naprawdę jest kaczką, czy jest iterowalna, czy nie, traktuj ją tak, jakby była, i narzekaj, jeśli nie była.

badp
źródło
3
Technicznie rzecz biorąc, podczas iteracji twoje obliczenia mogą wyrzucić TypeErrori rzucić cię tutaj, ale w zasadzie tak.
Chris Lutz
6
@willem: Proszę użyć timeit, aby wykonać test porównawczy. Wyjątki w Pythonie są często szybsze niż instrukcje if. Mogą przejść nieco krótszą ścieżkę przez tłumacza.
S.Lott,
2
@willem: IronPython ma powolne (w porównaniu do CPython) wyjątki.
jfs
2
Działająca próba: instrukcja jest naprawdę szybka. Więc jeśli masz kilka wyjątków, try-wyjątek jest szybki. Jeśli oczekujesz wielu wyjątków, „jeśli” może być szybsze.
Arne Babenhauserheide
2
Czy obiekt wyjątku nie powinien zostać przechwycony przez dodanie „ as e” po TypeErrorzamiast dodania „ , e”?
HelloGoodbye,
21

Od wersji Python 3.5 możesz używać modułu do pisania ze standardowej biblioteki do rzeczy związanych z typem:

from typing import Iterable

...

if isinstance(my_item, Iterable):
    print(True)
Rotareti
źródło
18

Najlepsze rozwiązanie, jakie do tej pory znalazłem:

hasattr(obj, '__contains__')

który w zasadzie sprawdza, czy obiekt implementuje inoperator.

Zalety (żadne z pozostałych rozwiązań nie ma wszystkich trzech):

  • jest to wyrażenie (działa jak lambda , w przeciwieństwie do try ... oprócz wariantu)
  • jest (powinien być) zaimplementowany przez wszystkie iteracje, w tym ciągi (w przeciwieństwie do __iter__)
  • działa na dowolnym Pythonie> = 2.5

Uwagi:

  • filozofia Pythona „prosić o wybaczenie, a nie pozwolenie” nie działa dobrze, gdy np. na liście masz zarówno iterabilne, jak i nie iteracyjne i musisz traktować każdy element inaczej w zależności od jego typu (traktuj iterowalne przy próbie i nie iterabety z wyjątkiem mogłyby działać, ale wyglądałyby brzydko i wprowadzały w błąd)
  • rozwiązania tego problemu, które próbują faktycznie iterować obiekt (np. [x dla x w obiekcie]) w celu sprawdzenia, czy jest iterowalny, mogą powodować znaczne kary wydajnościowe dla dużych iteracji (szczególnie jeśli potrzebujesz tylko kilku pierwszych elementów iteracji, dla przykład) i należy tego unikać
Vlad
źródło
3
Fajnie, ale dlaczego nie użyć modułu kolekcji, jak zaproponowano w stackoverflow.com/questions/1952464/... ? Wydaje mi się bardziej ekspresyjny.
Dave Abrahams
1
Jest krótszy (i nie wymaga dodatkowego importu) bez utraty jasności: posiadanie metody „zawiera” jest naturalnym sposobem na sprawdzenie, czy coś jest kolekcją obiektów.
Vlad
46
To, że coś może zawierać coś, niekoniecznie oznacza, że ​​można je powtarzać. Na przykład użytkownik może sprawdzić, czy punkt znajduje się w sześcianie 3D, ale w jaki sposób iterowałbyś przez ten obiekt?
Casey Kuball
13
To jest niepoprawne. Iterable sama nie obsługuje zawiera , co najmniej z Pythonem 3.4.
Peter Shinners,
15

Możesz spróbować:

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

Jeśli możemy stworzyć generator, który będzie iterował nad nim (ale nigdy nie będzie używać generatora, aby nie zajmował miejsca), będzie można go powtarzać. Wygląda na coś w rodzaju „duh”. Dlaczego musisz przede wszystkim ustalić, czy zmienna jest iterowalna?

Chris Lutz
źródło
Co iterable(itertools.repeat(0))? :)
badp
5
@badp, po (x for x in a)prostu tworzy generator, nie wykonuje żadnej iteracji na.
catchmeifyoutry
5
Czy próba jest (x for x in a)dokładnie równoważna z próbą iterator = iter(a)? Czy w niektórych przypadkach oba są różne?
maks.
Czy to nie for _ in a: breakjest prostsze? Czy to wolniej?
Mr_and_Mrs_D
2
@Mr_and_Mrs_D to źle, jeśli testowany obiekt jest iteratorem, który jest następnie iterowany (będzie krótszy o 1 element, ponieważ jego pozycji nie można zresetować), tworzenie generatorów śmieci nie iteruje obiektu, ponieważ nie są iterowane, chociaż nie jestem pewien, czy w 100% podniesie błąd typu, jeśli nie da się go powtórzyć.
Tcll,
13

Znalazłem ładne rozwiązanie tutaj :

isiterable = lambda obj: isinstance(obj, basestring) \
    or getattr(obj, '__iter__', False)
jbochi
źródło
10

Zgodnie ze Słowniczkiem Python 2 iterowalne są

wszystkie typy sekwencyjne (takie jak list, stri tuple) oraz niektóre rodzaje non-sekwencji, jak dicti filei obiektów wszelkich typów definiowanych ze związkiem __iter__()lub __getitem__()metody. Iterable mogą być używane w pętli for oraz w wielu innych miejscach, w których potrzebna jest sekwencja (zip (), map (), ...). Gdy obiekt iterowalny jest przekazywany jako argument do wbudowanej funkcji iter (), zwraca iterator dla obiektu.

Oczywiście, biorąc pod uwagę ogólny styl kodowania dla Pythona w oparciu o fakt, że „łatwiej prosić o wybaczenie niż o pozwolenie”, ogólnie oczekuje się, że użyje

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

Ale jeśli musisz to wyraźnie sprawdzić, możesz przetestować iterację przez hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). Musisz sprawdzić oba, ponieważ strnie mają __iter__metody (przynajmniej nie w Pythonie 2, w Pythonie 3 mają) i ponieważ generatorobiekty nie mają __getitem__metody.

Anafora
źródło
8

Często w moich skryptach wygodne jest definiowanie iterablefunkcji. (Teraz obejmuje sugerowane uproszczenie Alfe):

import collections

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

dzięki czemu możesz przetestować, czy dowolny obiekt jest iterowalny w bardzo czytelnej formie

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

tak jak w przypadku tej callablefunkcji

EDYCJA: jeśli masz zainstalowany numpy, możesz po prostu zrobić: z numpy import iterable, co jest po prostu czymś podobnym

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

Jeśli nie masz numpy, możesz po prostu zaimplementować ten kod lub powyższy.

fmonegaglia
źródło
3
Ilekroć robisz coś takiego if x: return True else: return False( xbo-boolean), możesz napisać to jako return x. W twoim przypadku return isinstance(…)bez żadnych if.
Alfe
Skoro przyznajesz, że rozwiązanie Alfe jest lepsze, dlaczego nie zmodyfikowałeś swojej odpowiedzi, żeby to powiedzieć? Zamiast tego masz teraz obie wersje w swojej odpowiedzi. Niepotrzebna gadatliwość. Przesyłanie zmiany w celu rozwiązania tego problemu.
ToolmakerSteve,
2
Powinieneś złapać "TypeError" w linii `oprócz: zwróć False`. Łapanie wszystkiego to zły wzór.
Mariusz Jamro
Wiedz to Przetłumaczyłem ten fragment kodu z biblioteki NumPy, która wykorzystuje ogólny wyjątek.
fmonegaglia,
Tylko dlatego, że kod jest pobierany z NumPy, nie oznacza, że ​​jest dobry ... wzorzec czy nie, jedyny czas na złapanie wszystkiego, co należy zrobić, to jeśli jawnie obsługuje się błędy w programie.
Tcll,
5

ma taką wbudowaną funkcję:

from pandas.util.testing import isiterable
Sören
źródło
wygląda to jednak tylko na to, czy __iter__tak naprawdę chodzi o sekwencje i tym podobne.
ead
4

Zawsze wymykało mi się pytanie, dlaczego python ma, callable(obj) -> boolale nie iterable(obj) -> bool... z
pewnością łatwiej to zrobić, hasattr(obj,'__call__')nawet jeśli jest wolniejszy.

Ponieważ prawie co druga odpowiedź zaleca używanie try/ except TypeError, gdzie testowanie wyjątków jest ogólnie uważane za złą praktykę w dowolnym języku, oto implementacja iterable(obj) -> bool, którą polubiłem i używam często:

Dla dobra Pythona 2 użyję lambda tylko dla tego dodatkowego zwiększenia wydajności ...
(w Pythonie 3 nie ma znaczenia, czego użyjesz do zdefiniowania funkcji, defma mniej więcej taką samą prędkość jak lambda)

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

Zauważ, że ta funkcja działa szybciej dla obiektów, __iter__ponieważ nie sprawdza __getitem__.

Większość iterowalnych obiektów powinna polegać na tym, do __iter__czego wracają obiekty ze specjalnych przypadków __getitem__, chociaż jest to wymagane, aby obiekt był iterowalny.
(a ponieważ jest to standard, wpływa również na obiekty C)

Tcll
źródło
nie zapewnia działającego kodu, nie mówiąc już o wydajności Pythona ... chociaż ta odpowiedź była tak naprawdę dla wygody, jak widziałem tutaj wiele razy.
Tcll
3
def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

To powie tak dla wszystkich rodzajów iterowalnych obiektów, ale powie nie dla łańcuchów w Pythonie 2 . (Tego właśnie chcę na przykład, gdy funkcja rekurencyjna może wziąć ciąg znaków lub kontener ciągów znaków. W takiej sytuacji prośba o wybaczenie może prowadzić do obfuscode i lepiej najpierw poprosić o pozwolenie).

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)

Wiele innych strategii mówi tak dla łańcuchów. Użyj ich, jeśli tego chcesz.

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

Uwaga: is_iterable () powie tak na ciągi znaków typu bytesi bytearray.

  • bytesobiekty w Pythonie 3 są iterowalne True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))W Pythonie 2 nie ma takiego typu.
  • bytearray obiekty w Pythonie 2 i 3 są iterowalne True == is_iterable(bytearray(b"abc"))

hasattr(x, '__iter__')Podejście OP powie tak dla ciągów w Pythonie 3 i nie w Pythonie 2 (bez względu na to, ''czy b''lub u''). Dzięki @LuisMasuelli za zauważenie, że zawiedzie Cię również na buggy __iter__.

Bob Stein
źródło
2

Najłatwiejszym sposobem, uwzględniając typ kaczki Pythona , jest złapanie błędu (Python doskonale wie, czego oczekuje od obiektu, aby stał się iteratorem):

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

Uwagi :

  1. Nie ma znaczenia rozróżnienie, czy obiekt nie jest iterowalny, czy __iter__zaimplementowano buggy , jeśli typ wyjątku jest taki sam: w każdym razie nie będzie można iterować obiektu.
  2. Wydaje mi się, że rozumiem twoją troskę: W jaki sposób callableistnieje sprawdzanie, czy mogę polegać na pisaniu kaczek, aby podnieść AttributeErrorif, który __call__nie jest zdefiniowany dla mojego obiektu, ale nie dotyczy to sprawdzania iteracyjnego?

    Nie znam odpowiedzi, ale możesz albo zaimplementować funkcję, którą dałem (i innym użytkownikom), albo po prostu złapać wyjątek w kodzie (implementacja w tej części będzie podobna do funkcji, którą napisałem - po prostu upewnij się, że izolujesz tworzenie iteratora z pozostałej części kodu, dzięki czemu można przechwycić wyjątek i odróżnić go od innego TypeError.

Luis Masuelli
źródło
1

Funkcja isiterablew poniższym kodzie zwraca, Truejeśli obiekt jest iterowalny. jeśli nie jest to iterowalne zwrotyFalse

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

przykład

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
Koczownik
źródło
2
tak wiele szczegółowych odpowiedzi powyżej z wieloma opiniami i wrzucasz niewyjaśnioną odpowiedź ... meh
Nrzonline
Nie publikuj samego kodu. Objaśnij także, co to robi.
Jonathan Mee,
1

Zamiast sprawdzać __iter__atrybut, można sprawdzić __len__atrybut, który jest implementowany przez każdą iterowalną wbudowaną wersję Pythona, w tym ciągi znaków.

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

Obiekty, których nie da się powtórzyć, nie wdrożyłyby tego z oczywistych powodów. Jednak nie przechwytuje iterałów zdefiniowanych przez użytkownika, które go nie implementują, ani nie wyraża wyrażeń generatora, z którymi itermożna sobie poradzić. Można to jednak zrobić w linii, a dodanie prostego orsprawdzania wyrażeń dla generatorów rozwiązałoby ten problem. (Pamiętaj, że pisanie type(my_generator_expression) == generatorrzuciłoby a NameError. Zamiast tego zapoznaj się z odpowiedzią)

Możesz użyć GeneratorType z typów:

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

--- zaakceptowana odpowiedź przez utdemir

(Przydaje się to jednak do sprawdzania, czy można wywołać lenobiekt).

DarthCadeus
źródło
niestety nie wszystkie iterowalne obiekty używają __len__... w tym przypadku zwykle jest to niewłaściwe użycie obliczania odległości między 2 obiektami. gdzie obj.dist()można łatwo zastąpić.
Tcll,
Tak. Większość zdefiniowanych przez użytkownika iteratów implementuje iter i getitem, ale nie len. Jednak wbudowane typy tak, a jeśli chcesz sprawdzić, czy możesz wywołać na nim funkcję len, sprawdzanie len jest bezpieczniejsze. Ale masz rację.
DarthCadeus
0

Niezupełnie „poprawne”, ale może służyć jako szybkie sprawdzenie najbardziej popularnych typów, takich jak łańcuchy, krotki, zmiennoprzecinkowe itp.

>>> '__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
źródło
0

Trochę późno na imprezę, ale zadałem sobie to pytanie i zobaczyłem to, a potem pomyślałem o odpowiedzi. Nie wiem, czy ktoś to już opublikował. Zasadniczo zauważyłem jednak, że wszystkie typy iterowalne mają w swoim słowniku __getitem __ () . W ten sposób można sprawdzić, czy obiekt był iterowalny, nawet nie próbując. (Kalambur przeznaczony)

def is_attr(arg):
    return '__getitem__' in dir(arg)
lakam99
źródło
Niestety jest to niewiarygodne. Przykład
timgeb
1
Obiekty ustawione to kolejny kontrprzykład.
Raymond Hettinger