Dengan Python, bagaimana cara menentukan apakah suatu objek dapat diubah?

1085

Apakah ada metode seperti itu isiterable? Satu-satunya solusi yang saya temukan sejauh ini adalah menelepon

hasattr(myObj, '__iter__')

Tapi saya tidak yakin betapa bodohnya ini.

Willem
sumber
18
__getitem__juga cukup untuk membuat objek dapat diubah
Kos
4
FWIW: iter(myObj)berhasil jika isinstance(myObj, dict), jadi jika Anda melihat sebuah myObjyang bisa menjadi urutan dicts atau tunggal dict, Anda akan berhasil dalam kedua kasus. Kehalusan yang penting jika Anda ingin tahu apa itu urutan dan apa yang tidak. (dalam Python 2)
Ben Mosher
7
__getitem__juga cukup untuk membuat objek dapat diubah ... jika dimulai dengan indeks nol .
Carlos A. Gómez

Jawaban:

28

Saya telah mempelajari masalah ini agak belakangan ini. Berdasarkan kesimpulan saya adalah bahwa saat ini ini adalah pendekatan terbaik:

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

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

Hal di atas telah direkomendasikan sebelumnya, tetapi konsensus umum adalah bahwa menggunakan iter()akan lebih baik:

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

Kami telah menggunakan iter()kode kami juga untuk tujuan ini, tetapi akhir-akhir ini saya mulai semakin terganggu oleh objek yang hanya __getitem__dianggap dapat diubah. Ada alasan valid untuk memiliki objek __getitem__yang tidak dapat diubah dan dengan mereka kode di atas tidak berfungsi dengan baik. Sebagai contoh kehidupan nyata kita bisa menggunakan Faker . Kode di atas melaporkannya iterable tetapi sebenarnya mencoba iterate yang menyebabkannya AttributeError(diuji dengan 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'

Jika kami akan menggunakan insinstance(), kami tidak akan secara tidak sengaja menganggap instance Faker (atau objek lain yang hanya memiliki __getitem__) untuk diubah:

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

Jawaban sebelumnya berkomentar bahwa menggunakan iter()lebih aman karena cara lama untuk menerapkan iterasi dengan Python didasarkan __getitem__dan isinstance()pendekatan tidak akan mendeteksi itu. Ini mungkin benar dengan versi Python lama, tetapi berdasarkan pengujian yang cukup lengkap saya isinstance()bekerja sangat baik saat ini. Satu-satunya kasus di mana isinstance()tidak bekerja tetapi iter()lakukan adalah dengan UserDictketika menggunakan Python 2. Jika itu relevan, itu mungkin digunakan isinstance(item, (Iterable, UserDict))untuk mendapatkan yang tertutup.

Pekka Klärck
sumber
1
Juga typing.Dictdianggap iterable oleh iter(Dict)tetapi list(Dict)gagal dengan kesalahan TypeError: Parameters to generic types must be types. Got 0.. Seperti yang diharapkan isinstance(Dict, Iterable)mengembalikan false.
Pekka Klärck
1
Saya sampai pada kesimpulan yang sama, tetapi karena berbagai alasan. Penggunaan itermenyebabkan beberapa kode kami yang menggunakan "pre-caching" melambat secara tidak perlu. Jika __iter__kodenya lambat, maka akan memanggil iter... kapan saja Anda hanya ingin melihat apakah ada sesuatu yang dapat diubah.
thorwhalen
842
  1. Memeriksa __iter__karya pada tipe urutan, tetapi akan gagal misalnya string di Python 2 . Saya ingin tahu jawaban yang benar juga, sampai saat itu, di sini ada satu kemungkinan (yang juga bisa digunakan pada string):

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

    The iterbuilt-in cek untuk __iter__metode atau dalam kasus string yang __getitem__metode.

  2. Pendekatan pythonic umum lainnya adalah dengan menganggap iterable, kemudian gagal dengan anggun jika tidak bekerja pada objek yang diberikan. Daftar istilah Python:

    Gaya pemrograman Pythonic yang menentukan tipe objek dengan memeriksa metode atau atribut signature daripada dengan hubungan eksplisit ke beberapa tipe objek ("Jika terlihat seperti bebek dan dukun seperti bebek , itu pasti bebek .") Dengan menekankan antarmuka daripada tipe tertentu, kode yang dirancang dengan baik meningkatkan fleksibilitasnya dengan memungkinkan substitusi polimorfik. Mengetik bebek menghindari tes menggunakan type () atau isinstance ().Sebaliknya, ia biasanya menggunakan gaya pemrograman EAFP (Lebih Mudah untuk Meminta Maaf daripada Izin).

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
  3. The collectionsmodul menyediakan beberapa kelas dasar abstrak, yang memungkinkan untuk meminta kelas atau contoh jika mereka menyediakan fungsionalitas tertentu, misalnya:

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

    Namun, ini tidak memeriksa untuk kelas yang dapat diulangi __getitem__.

miku
sumber
34
[e for e in my_object]dapat menimbulkan pengecualian karena alasan lain, yaitu my_objecttidak terdefinisi atau kemungkinan bug dalam my_objectimplementasi.
Nick Dandoulakis
37
Suatu string adalah suatu urutan ( isinstance('', Sequence) == True) dan sebagaimana urutan apa pun itu dapat diubah ( isinstance('', Iterable)). Padahal hasattr('', '__iter__') == Falsedan itu mungkin membingungkan.
jfs
82
Jika my_objectsangat besar (misalnya, tak terbatas seperti itertools.count()) pemahaman daftar Anda akan memakan banyak waktu / memori. Lebih baik membuat generator, yang tidak akan pernah mencoba membangun daftar (berpotensi tak terbatas).
Chris Lutz
14
Bagaimana jika some_object melempar TypeError yang disebabkan oleh alasan lain (bug dll) juga? Bagaimana kita bisa tahu dari "NotErable TypeError"?
Shaung
54
Perhatikan bahwa dalam Python 3: hasattr(u"hello", '__iter__')pengembalianTrue
Carlos
572

Mengetik bebek

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

# for obj in iterator:
#     pass

Ketik memeriksa

Gunakan Kelas Basis Abstrak . Mereka membutuhkan setidaknya Python 2.6 dan hanya berfungsi untuk kelas gaya baru.

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

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

Namun, iter()sedikit lebih dapat diandalkan seperti yang dijelaskan oleh dokumentasi :

Memeriksa isinstance(obj, Iterable)mendeteksi kelas yang terdaftar sebagai Iterable atau yang memiliki __iter__()metode, tetapi tidak mendeteksi kelas yang mengulangi dengan __getitem__() metode. Satu-satunya cara yang dapat diandalkan untuk menentukan apakah suatu objek dapat diubah adalah dengan menelepon iter(obj).

Georg Schölly
sumber
18
Dari "Fluent Python" oleh Luciano Ramalho: Pada Python 3.4, cara paling akurat untuk memeriksa apakah suatu objek x dapat diubah adalah dengan memanggil iter (x) dan menangani pengecualian TypeError jika tidak. Ini lebih akurat daripada menggunakan isinstance (x, abc.Iterable), karena iter (x) juga mempertimbangkan metode legacy getitem , sedangkan Iterable ABC tidak.
RdB
Jika Anda berpikir "oh saya hanya akan isinstance(x, (collections.Iterable, collections.Sequence))ganti iter(x)", perhatikan bahwa ini masih tidak akan mendeteksi objek iterable yang mengimplementasikan hanya __getitem__tetapi tidak __len__. Gunakan iter(x)dan tangkap pengecualian.
Dale
Jawaban kedua Anda tidak berhasil. Di PyUNO jika saya lakukan iter(slide1), semuanya berjalan dengan baik, namun isinstance(slide1, Iterable)lemparannya TypeError: issubclass() arg 1 must be a class.
Hi-Angel
@ Hai-Angel terdengar seperti bug di PyUNOPerhatikan bahwa pesan kesalahan Anda mengatakan issubclass()bukan isinstance().
Georg Schölly
2
Memanggil iter () di atas objek bisa menjadi operasi yang mahal (lihat DataLoader di Pytorch, yang mem-fork / memunculkan beberapa proses pada iter ()).
szali
126

Saya ingin menumpahkan sedikit yang sedikit lebih terang pada interaksi iter, __iter__dan __getitem__dan apa yang terjadi di balik tirai. Berbekal pengetahuan itu, Anda akan dapat memahami mengapa yang terbaik yang bisa Anda lakukan adalah

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

Saya akan mendaftar fakta terlebih dahulu dan kemudian menindaklanjuti dengan pengingat cepat tentang apa yang terjadi ketika Anda mempekerjakan seorang for loop dalam python, diikuti dengan diskusi untuk menggambarkan fakta-fakta.

Fakta

  1. Anda bisa mendapatkan iterator dari objek apa pun odengan memanggil iter(o)jika setidaknya salah satu dari kondisi berikut ini berlaku:

    a) omemiliki __iter__metode yang mengembalikan objek iterator. Iterator adalah objek apa pun dengan metode __iter__dan __next__(Python 2 next:).

    b) opunya __getitem__metode.

  2. Memeriksa instance Iterableatau Sequence, atau memeriksa atribut __iter__tidak cukup.

  3. Jika suatu objek ohanya mengimplementasikan __getitem__, tetapi tidak __iter__, iter(o)akan membangun iterator yang mencoba mengambil item dari odengan indeks integer, mulai dari indeks 0. Iterator akan menangkap setiap IndexError(tetapi tidak ada kesalahan lain) yang dinaikkan dan kemudian memunculkan StopIterationdirinya sendiri.

  4. Dalam arti yang paling umum, tidak ada cara untuk memeriksa apakah iterator yang dikembalikan oleh iterwaras selain untuk mencobanya.

  5. Jika suatu objek omengimplementasikan __iter__, iterfungsi akan memastikan bahwa objek yang dikembalikan oleh __iter__iterator. Tidak ada pemeriksaan kewarasan jika suatu objek hanya mengimplementasikan __getitem__.

  6. __iter__menang. Jika suatu objek omengimplementasikan keduanya __iter__dan __getitem__, iter(o)akan memanggil __iter__.

  7. Jika Anda ingin membuat objek Anda sendiri dapat diubah, selalu terapkan __iter__metode ini.

for loop

Untuk mengikuti, Anda perlu pemahaman tentang apa yang terjadi ketika Anda menggunakan forloop dengan Python. Jangan ragu untuk langsung ke bagian selanjutnya jika Anda sudah tahu.

Ketika Anda menggunakan for item in ountuk beberapa objek iterable o, Python memanggil iter(o)dan mengharapkan objek iterator sebagai nilai balik. Iterator adalah objek apa pun yang mengimplementasikan metode __next__(dan nextdengan Python 2) dan __iter__metode.

Dengan konvensi, __iter__metode iterator harus mengembalikan objek itu sendiri (yaitu return self). Python kemudian memanggil nextiterator hingga StopIterationdinaikkan. Semua ini terjadi secara implisit, tetapi demonstrasi berikut membuatnya terlihat:

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

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

Diskusi dan ilustrasi

Pada poin 1 dan 2: mendapatkan iterator dan cek tidak dapat diandalkan

Pertimbangkan kelas berikut:

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

Menelepon iterdengan instance of BasicIterableakan mengembalikan iterator tanpa masalah karena BasicIterableimplementasinya __getitem__.

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

Namun, penting untuk dicatat bahwa btidak memiliki __iter__atribut dan tidak dianggap sebagai contoh Iterableatau Sequence:

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

Inilah sebabnya mengapa Fluent Python oleh Luciano Ramalho merekomendasikan pemanggilan iterdan penanganan potensi TypeErrorsebagai cara paling akurat untuk memeriksa apakah suatu objek dapat diubah. Mengutip langsung dari buku:

Pada Python 3.4, cara paling akurat untuk memeriksa apakah suatu objek xdapat diubah adalah dengan memanggil iter(x)dan menangani TypeErrorpengecualian jika tidak. Ini lebih akurat daripada menggunakan isinstance(x, abc.Iterable), karena iter(x)juga mempertimbangkan __getitem__metode warisan , sedangkan IterableABC tidak.

Pada poin 3: Iterasi objek yang hanya menyediakan __getitem__, tetapi tidak__iter__

Iterasi atas instance BasicIterablekarya seperti yang diharapkan: Python membangun iterator yang mencoba mengambil item menurut indeks, mulai dari nol, hingga sebuah IndexErrordinaikkan. Metode objek demo __getitem__hanya mengembalikan itemyang diberikan sebagai argumen __getitem__(self, item)oleh iterator yang dikembalikan oleh 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

Perhatikan bahwa iterator meningkat StopIterationketika tidak dapat mengembalikan item berikutnya dan item IndexErroryang dinaikkan item == 3ditangani secara internal. Inilah sebabnya mengapa pengulangan BasicIterabledengan sebuah forpengulangan bekerja seperti yang diharapkan:

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

Berikut adalah contoh lain untuk mengembalikan konsep bagaimana iterator dikembalikan dengan itermencoba mengakses item berdasarkan indeks. WrappedDicttidak mewarisi dari dict, yang berarti instance tidak akan memiliki __iter__metode.

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

Perhatikan bahwa panggilan untuk __getitem__didelegasikan ke dict.__getitem__mana notasi braket persegi hanyalah sebuah singkatan.

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

Pada poin 4 dan 5: itermemeriksa iterator ketika memanggil__iter__ :

Ketika iter(o)dipanggil untuk suatu objek o, iterakan memastikan bahwa nilai balik dari __iter__, jika metode ini ada, adalah iterator. Ini berarti bahwa objek yang dikembalikan harus mengimplementasikan __next__(atau nextdengan Python 2) dan __iter__. itertidak dapat melakukan pemeriksaan kewarasan untuk objek yang hanya menyediakan __getitem__, karena tidak memiliki cara untuk memeriksa apakah item objek dapat diakses oleh indeks integer.

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

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

Perhatikan bahwa membangun iterator dari FailIterIterableinstance gagal dengan segera, saat membangun iterator dari FailGetItemIterableberhasil, tetapi akan membuang Pengecualian pada panggilan pertama ke __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

Pada poin 6: __iter__menang

Yang ini mudah. Jika suatu objek mengimplementasikan __iter__dan __getitem__, iterakan memanggil __iter__. Pertimbangkan kelas berikut

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

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

dan output ketika mengulang contoh:

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

Pada poin 7: kelas iterable Anda harus diimplementasikan __iter__

Anda mungkin bertanya pada diri sendiri mengapa sebagian besar urutan builtin seperti listmengimplementasikan suatu __iter__metode padahal __getitem__sudah cukup.

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

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

Bagaimanapun, iterasi atas instance dari kelas di atas, yang mendelegasikan panggilan __getitem__ke list.__getitem__(menggunakan notasi braket persegi), akan berfungsi dengan baik:

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

Alasan iterables kustom Anda harus menerapkan __iter__adalah sebagai berikut:

  1. Jika Anda menerapkan __iter__, instance akan dianggap iterables, dan isinstance(o, collections.abc.Iterable)akan kembali True.
  2. Jika objek yang dikembalikan oleh __iter__bukan iterator, iterakan gagal segera dan menaikkan a TypeError.
  3. Penanganan khusus __getitem__ada untuk alasan kompatibilitas ke belakang. Mengutip lagi dari Fluent Python:

Itulah sebabnya setiap urutan Python dapat diubah: mereka semua mengimplementasikan __getitem__. Bahkan, urutan standar juga diterapkan __iter__, dan Anda harus melakukannya juga, karena penanganan khusus __getitem__ada karena alasan kompatibilitas ke belakang dan mungkin hilang di masa depan (meskipun tidak ditinggalkan saat saya menulis ini).

timgeb
sumber
jadi aman untuk mendefinisikan predikat is_iterabledengan kembali Truedi tryblok dan Falsedi except TypeErrorblok?
alancalvitti
Ini jawaban yang bagus. Saya pikir ini menyoroti sifat protokol getitem yang tidak intuitif dan tidak menguntungkan. Seharusnya tidak pernah ditambahkan.
Neil G
31

Ini tidak cukup: objek yang dikembalikan oleh __iter__harus mengimplementasikan protokol iterasi (yaitu nextmetode). Lihat bagian yang relevan dalam dokumentasi .

Dalam Python, praktik yang baik adalah "mencoba dan melihat" alih-alih "memeriksa".

jldupont
sumber
9
"bebek mengetik", saya percaya? :)
willem
9
@willem: atau "tidak meminta izin tetapi untuk pengampunan" ;-)
jldupont
14
@willem Gaya "izin" dan "pengampunan" memenuhi syarat sebagai pengetikan bebek. Jika Anda bertanya apa sebuah objek dapat melakukan daripada apa yang , bahwa mengetik bebek. Jika Anda menggunakan introspeksi, itu "izin"; jika Anda hanya mencoba melakukannya dan melihat apakah itu berhasil atau tidak, itu "pengampunan".
Mark Reed
22

Dalam Python <= 2.5, Anda tidak bisa dan tidak boleh - iterable adalah antarmuka "informal".

Tetapi karena Python 2.6 dan 3.0 Anda dapat memanfaatkan infrastruktur ABC (kelas dasar abstrak) yang baru bersama dengan beberapa ABC bawaan yang tersedia dalam modul koleksi:

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)

Sekarang, apakah ini diinginkan atau benar-benar berfungsi, hanyalah masalah konvensi. Seperti yang Anda lihat, Anda bisa mendaftarkan objek yang tidak dapat diubah sebagai Iterable - dan itu akan menimbulkan pengecualian saat runtime. Oleh karena itu, isinstance memperoleh makna "baru" - itu hanya memeriksa untuk kompatibilitas jenis "dinyatakan", yang merupakan cara yang baik untuk menggunakan Python.

Di sisi lain, jika objek Anda tidak memenuhi antarmuka yang Anda butuhkan, apa yang akan Anda lakukan? Ambil contoh berikut:

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

Jika objek tidak memenuhi apa yang Anda harapkan, Anda cukup melempar TypeError, tetapi jika ABC yang tepat telah terdaftar, cek Anda tidak berguna. Sebaliknya, jika __iter__metode ini tersedia, Python akan secara otomatis mengenali objek kelas itu sebagai Iterable.

Jadi, jika Anda hanya mengharapkan iterable, beralihlah dan lupakan saja. Di sisi lain, jika Anda perlu melakukan hal-hal yang berbeda tergantung pada tipe input, Anda mungkin menemukan infrastruktur ABC cukup berguna.

Alan Franzoni
sumber
13
jangan gunakan telanjang except:di kode contoh untuk pemula. Ini mempromosikan praktik buruk.
jfs
JFS: Saya tidak mau, tapi saya harus melalui beberapa kode peningkatan pengecualian dan saya tidak ingin menangkap pengecualian spesifik ... Saya pikir tujuan dari kode ini cukup jelas.
Alan Franzoni
21
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

Jangan menjalankan cek untuk melihat apakah bebek Anda benar-benar bebek untuk melihat apakah iterable atau tidak, perlakukan itu seolah-olah itu dan mengeluh jika tidak.

badp
sumber
3
Secara teknis, selama iterasi perhitungan Anda mungkin melempar TypeErrordan melemparkan Anda ke sini, tetapi pada dasarnya ya.
Chris Lutz
6
@willem: Silakan gunakan timeit untuk melakukan benchmark. Pengecualian Python seringkali lebih cepat daripada pernyataan if. Mereka bisa mengambil jalan yang sedikit lebih pendek melalui penerjemah.
S.Lott
2
@willem: IronPython memiliki pengecualian yang lambat (dibandingkan dengan CPython).
jfs
2
Percobaan yang berhasil: pernyataan sangat cepat. Jadi, jika Anda memiliki beberapa pengecualian, coba-kecuali itu cepat. Jika Anda mengharapkan banyak pengecualian, "jika" bisa lebih cepat.
Arne Babenhauserheide
2
Bukankah seharusnya objek pengecualian ditangkap dengan menambahkan " as e" setelah TypeErroralih-alih dengan menambahkan " , e"?
HelloGoodbye
21

Karena Python 3.5 Anda dapat menggunakan modul pengetikan dari perpustakaan standar untuk mengetik hal-hal terkait:

from typing import Iterable

...

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

Solusi terbaik yang saya temukan sejauh ini:

hasattr(obj, '__contains__')

yang pada dasarnya memeriksa apakah objek mengimplementasikan inoperator.

Keuntungan (tidak ada solusi lain yang memiliki ketiganya):

  • itu adalah ekspresi (berfungsi sebagai lambda , berbeda dengan percobaan ... kecuali varian)
  • itu (harus) diimplementasikan oleh semua iterables, termasuk string (sebagai lawan dari __iter__)
  • bekerja pada sembarang Python> = 2.5

Catatan:

  • filosofi Python "meminta pengampunan, bukan izin" tidak berfungsi dengan baik ketika misalnya dalam daftar Anda memiliki iterables dan non-iterables dan Anda perlu memperlakukan setiap elemen secara berbeda sesuai dengan tipenya (memperlakukan iterables saat dicoba dan tidak iterables pada kecuali akan bekerja, tetapi akan terlihat butt-jelek dan menyesatkan)
  • solusi untuk masalah ini yang berusaha untuk benar-benar beralih pada objek (misalnya [x untuk x dalam obj]) untuk memeriksa apakah itu dapat diubah dapat menyebabkan hukuman kinerja yang signifikan untuk iterables besar (terutama jika Anda hanya memerlukan beberapa elemen pertama dari iterable, untuk contoh) dan harus dihindari
Vlad
sumber
3
Bagus, tapi mengapa tidak menggunakan modul koleksi seperti yang diusulkan di stackoverflow.com/questions/1952464/… ? Tampak lebih ekspresif bagi saya.
Dave Abrahams
1
Ini lebih pendek (dan tidak memerlukan impor tambahan) tanpa kehilangan kejelasan: memiliki metode "mengandung" terasa seperti cara alami untuk memeriksa apakah ada koleksi benda.
Vlad
46
Hanya karena sesuatu dapat mengandung sesuatu tidak selalu berarti itu dapat diubah. Misalnya, pengguna dapat memeriksa apakah suatu titik ada dalam kubus 3D, tetapi bagaimana Anda akan mengulanginya melalui objek ini?
Casey Kuball
13
Ini salah. Iterable sendiri tidak mendukung berisi , setidaknya dengan Python 3.4.
Peter Shinners
15

Anda bisa mencoba ini:

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

Jika kita bisa membuat generator yang beralih di atasnya (tapi jangan pernah menggunakan generator sehingga tidak memakan tempat), itu bisa diubah. Sepertinya itu semacam "duh". Mengapa Anda perlu menentukan apakah suatu variabel dapat diubah sejak awal?

Chris Lutz
sumber
Bagaimana dengan iterable(itertools.repeat(0))? :)
badp
5
@badp, yang (x for x in a)baru saja membuat generator, itu tidak melakukan iterasi pada a.
catchmeifyoutry
5
Apakah berusaha (x for x in a)sama dengan mencoba iterator = iter(a)? Atau ada beberapa kasus di mana keduanya berbeda?
maks
Bukankah for _ in a: breaklebih mudah? Apakah lebih lambat?
Mr_and_Mrs_D
2
@Mr_and_Mrs_D itu buruk jika objek yang diuji adalah iterator yang iterated setelahnya (itu akan menjadi 1 item pendek karena posisinya tidak dapat diatur ulang), membuat generator sampah tidak beralih pada objek karena mereka tidak diulangi, meskipun saya tidak yakin bahwa itu akan meningkatkan 100% TypeError jika tidak dapat diperbaiki.
Tcll
13

Saya menemukan solusi yang bagus di sini :

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

Menurut Python 2 Glosarium , iterables adalah

semua urutan jenis (seperti list, str, dan tuple) dan beberapa non-urutan jenis seperti dictdan filedan objek dari setiap kelas yang Anda mendefinisikan dengan __iter__()atau __getitem__()metode. Iterables dapat digunakan dalam for for dan di banyak tempat lain di mana diperlukan urutan (zip (), map (), ...). Ketika objek iterable diteruskan sebagai argumen ke fungsi built-in iter (), ia mengembalikan iterator untuk objek.

Tentu saja, mengingat gaya pengkodean umum untuk Python berdasarkan pada fakta bahwa “Lebih mudah untuk meminta pengampunan daripada izin.”, Harapan umum adalah menggunakan

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

Tetapi jika Anda perlu memeriksanya secara eksplisit, Anda dapat menguji untuk iterable oleh hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). Anda perlu memeriksa keduanya, karena strs tidak memiliki __iter__metode (setidaknya tidak dalam Python 2, dalam Python 3 yang mereka lakukan) dan karena generatorobjek tidak memiliki __getitem__metode.

Anaforis
sumber
8

Saya sering merasa nyaman, di dalam skrip saya, untuk mendefinisikan suatu iterablefungsi. (Sekarang memasukkan penyederhanaan yang disarankan Alfe):

import collections

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

sehingga Anda dapat menguji apakah ada objek yang dapat diubah dalam bentuk yang sangat mudah dibaca

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

seperti yang akan Anda lakukan dengancallable fungsi

EDIT: jika Anda telah menginstal numpy, Anda dapat melakukan: from numpy import iterable, yang merupakan sesuatu seperti

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

Jika Anda tidak memiliki numpy, Anda dapat menerapkan kode ini, atau yang di atas.

fmonegaglia
sumber
3
Setiap kali Anda suka if x: return True else: return False(dengan xmenjadi boolean) Anda dapat menulis ini sebagai return x. Dalam kasus Anda return isinstance(…)tanpa if.
Alfe
Karena Anda mengakui bahwa solusi Alfe lebih baik, mengapa Anda tidak mengedit jawaban Anda hanya dengan mengatakan itu? Sebaliknya, Anda sekarang memiliki KEDUA versi dalam jawaban Anda. Verbositas yang tidak perlu. Mengirimkan edit untuk memperbaikinya.
ToolmakerSteve
2
Anda harus menangkap "TypeError" di baris `kecuali: return False`. Menangkap semuanya adalah pola yang buruk.
Mariusz Jamro
Tahu bahwa. Saya menerjemahkan potongan kode dari perpustakaan NumPy, yang menggunakan pengecualian umum.
fmonegaglia
Hanya karena kode diambil dari NumPy tidak berarti itu bagus ... pola atau tidak, satu-satunya waktu menangkap semuanya harus dilakukan adalah jika Anda secara eksplisit menangani kesalahan di dalam program Anda.
Tcll
5

memiliki fungsi bawaan seperti itu:

from pandas.util.testing import isiterable
Sören
sumber
Namun ini hanya melihat apakah ada __iter__dan tidak terlalu peduli tentang urutan dan sejenisnya.
ead
4

Itu selalu menghindari saya mengapa python memiliki callable(obj) -> booltetapi tidak iterable(obj) -> bool...
pasti lebih mudah untuk dilakukan hasattr(obj,'__call__')bahkan jika lebih lambat.

Karena hampir setiap jawaban lain merekomendasikan penggunaan try/ except TypeError, di mana pengujian untuk pengecualian umumnya dianggap praktik yang buruk di antara bahasa apa pun, berikut ini adalah implementasi dari iterable(obj) -> boolsaya telah semakin menyukai dan sering menggunakan:

Demi python 2, saya akan menggunakan lambda hanya untuk meningkatkan kinerja ekstra ...
(dalam python 3 tidak masalah apa yang Anda gunakan untuk mendefinisikan fungsi, defmemiliki kecepatan yang kira-kira sama lambda)

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

Perhatikan bahwa fungsi ini dieksekusi lebih cepat untuk objek dengan __iter__karena tidak diuji __getitem__.

Sebagian besar objek yang dapat diubah harus bergantung pada __iter__tempat objek kasus khusus jatuh kembali __getitem__, meskipun salah satu diperlukan untuk objek yang dapat diulang.
(dan karena ini standar, itu juga mempengaruhi objek C)

Tcll
sumber
dia tidak memberikan kode yang berfungsi, apalagi berbicara tentang kinerja python ... walaupun jawaban ini benar-benar hanya untuk kenyamanan seperti yang saya lihat dilakukan berulang kali di sini.
Tcll
3
def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

Ini akan mengatakan ya untuk segala macam objek yang dapat diubah, tetapi akan mengatakan tidak pada string dalam Python 2 . (Itulah yang saya inginkan misalnya ketika fungsi rekursif dapat mengambil string atau wadah string. Dalam situasi itu, meminta maaf dapat menyebabkan obfuscode, dan lebih baik meminta izin terlebih dahulu.)

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)

Banyak strategi lain di sini akan mengatakan ya untuk string. Gunakan mereka jika itu yang Anda inginkan.

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

Catatan: is_iterable () akan mengatakan ya untuk string tipe bytesdan bytearray.

  • bytesobjek dalam Python 3 dapat diulang True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))Tidak ada jenis dalam Python 2.
  • bytearray objek dalam Python 2 dan 3 dapat diubah True == is_iterable(bytearray(b"abc"))

OP hasattr(x, '__iter__')pendekatan akan mengatakan ya untuk string di Python 3 dan tidak ada di Python 2 (tidak peduli apakah ''atau b''atau u''). Terima kasih kepada @LuisMasuelli karena memperhatikannya juga akan mengecewakan Anda dengan kereta __iter__.

Bob Stein
sumber
2

Cara termudah, dengan menghormati pengetikan bebek Python , adalah dengan menangkap kesalahan (Python tahu benar apa yang diharapkan dari suatu objek untuk menjadi iterator):

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

Catatan :

  1. Itu tidak relevan pembedaan apakah objek tidak iterable, atau buggy __iter__telah diimplementasikan, jika tipe pengecualiannya sama: toh Anda tidak akan dapat mengulangi objek.
  2. Saya rasa saya mengerti kekhawatiran Anda: Bagaimana bisa callableada sebagai cek jika saya juga bisa mengandalkan pengetikan bebek untuk menaikkan AttributeErrorjika __call__tidak ditentukan untuk objek saya, tapi itu tidak berlaku untuk pengecekan yang dapat diubah?

    Saya tidak tahu jawabannya, tetapi Anda bisa mengimplementasikan fungsi yang saya (dan pengguna lain) berikan, atau hanya menangkap pengecualian dalam kode Anda (implementasi Anda di bagian itu akan seperti fungsi yang saya tulis - pastikan Anda mengisolasi pembuatan iterator dari sisa kode sehingga Anda dapat menangkap pengecualian dan membedakannya dari yang lain TypeError.

Luis Masuelli
sumber
1

The isiterablefunc di pengembalian kode berikut Truejika objek adalah iterable. jika bukan pengembalian yang bisa dikembalikanFalse

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

contoh

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
Pengembara
sumber
2
begitu banyak jawaban terperinci di atas dengan banyak upvotes dan Anda memberikan jawaban yang tidak dapat dijelaskan ... meh
Nrzonline
Tolong jangan memposting kode telanjang. Juga termasuk penjelasan tentang apa yang dilakukan ini.
Jonathan Mee
1

Alih-alih memeriksa __iter__atribut, Anda bisa memeriksa __len__atribut, yang diimplementasikan oleh setiap python yang dibangun iterable, termasuk string.

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

Objek yang tidak dapat diubah tidak akan mengimplementasikan ini karena alasan yang jelas. Namun, itu tidak menangkap iterables yang ditentukan pengguna yang tidak mengimplementasikannya, atau ekspresi generator, yang iterdapat menangani. Namun, ini dapat dilakukan dalam satu baris, dan menambahkan orekspresi sederhana memeriksa generator akan memperbaiki masalah ini. (Perhatikan bahwa tulisan type(my_generator_expression) == generatorakan melempar NameError. Rujuk ke jawaban ini sebagai gantinya.)

Anda dapat menggunakan GeneratorType dari jenis:

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

--- jawaban yang diterima oleh utdemir

(Ini membuatnya berguna untuk memeriksa apakah Anda dapat memanggil lenobjek itu.)

DarthCadeus
sumber
sayangnya tidak semua objek yang dapat diubah menggunakan __len__... untuk kasus ini, biasanya penggunaan jarak penghitungan yang tidak benar antara 2 objek. di mana obj.dist()bisa dengan mudah diganti.
Tcll
Ya. Sebagian besar pengguna mendefinisikan iterables mengimplementasikan iter dan getitem tetapi tidak len. Namun, tipe bawaan memang ada, dan jika Anda ingin memeriksa apakah Anda dapat memanggil fungsi len di atasnya, memeriksa len lebih aman. Tapi kamu benar.
DarthCadeus
0

Tidak benar-benar "benar" tetapi dapat berfungsi sebagai pemeriksaan cepat dari jenis yang paling umum seperti string, tuple, float, dll ...

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

Agak terlambat ke pesta tetapi saya bertanya pada diri sendiri pertanyaan ini dan melihat ini kemudian memikirkan jawaban. Saya tidak tahu apakah seseorang sudah memposting ini. Tetapi pada dasarnya, saya perhatikan bahwa semua jenis iterable memiliki __getitem __ () dict mereka. Ini adalah bagaimana Anda akan memeriksa apakah suatu objek itu dapat diubah tanpa mencoba. (Pun intended)

def is_attr(arg):
    return '__getitem__' in dir(arg)
Lakam99
sumber
Sayangnya, ini tidak bisa diandalkan. Contoh
timgeb
1
Mengatur objek adalah contoh tandingan lain.
Raymond Hettinger