Pertanyaan Dengan Python, bagaimana saya menentukan apakah suatu objek dapat diulangi?


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

hasattr(myObj, '__iter__')

Tapi saya tidak yakin betapa bodohnya ini.


802
2017-12-23 12:13


asal


Jawaban:


  1. Memeriksa __iter__ berfungsi pada jenis urutan, tetapi akan gagal pada mis. string dengan Python 2. Saya ingin tahu jawaban yang benar juga, sampai saat itu, di sini adalah satu kemungkinan (yang juga akan bekerja pada string):

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

    Itu iter pemeriksaan bawaan untuk __iter__ metode atau dalam kasus string __getitem__ metode.

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

    Gaya pemrograman Pythonic yang menentukan jenis objek dengan pemeriksaan metode atau tanda tangan atributnya alih-alih dengan hubungan eksplisit ke beberapa objek jenis ("Jika terlihat seperti bebek dan dukun seperti bebek, itu pasti sebuah bebek. ") Dengan menekankan antarmuka daripada jenis tertentu, kode yang dirancang dengan baik meningkatkan fleksibilitasnya dengan memungkinkan substitusi polimorfik. Bebek-mengetik menghindari tes menggunakan jenis () atau isinstance (). Sebaliknya, biasanya menggunakan gaya pemrograman EAFP (Lebih Mudah Meminta Pengampunan daripada Izin).

    ...

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

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

    Namun, ini tidak memeriksa kelas yang dapat diteruskan __getitem__.


644
2017-12-23 12:16



Bebek mengetik

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

# for obj in iterator:
#     pass

Ketik pemeriksaan

Menggunakan Kelas Basis Abstrak. Mereka membutuhkan setidaknya Python 2.6 dan hanya bekerja untuk kelas-kelas gaya baru.

import collections

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

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

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


491
2017-12-23 12:53



Saya ingin melepaskan sedikit lebih banyak cahaya pada interaksi iter, __iter__ dan __getitem__ dan apa yang terjadi di balik tirai. Berbekal pengetahuan itu, Anda akan dapat memahami mengapa yang terbaik yang dapat Anda lakukan adalah

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

Saya akan membuat daftar fakta terlebih dahulu dan kemudian menindaklanjutinya dengan pengingat cepat tentang apa yang terjadi ketika Anda menggunakan for loop dalam python, diikuti dengan diskusi untuk mengilustrasikan fakta.

Fakta

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

    Sebuah) o memiliki __iter__ metode yang mengembalikan objek iterator. Sebuah iterator adalah objek apa pun dengan suatu __iter__ dan a __next__ (Python 2: next) metode.

    b) o mempunyai sebuah __getitem__ metode.

  2. Memeriksa contoh dari Iterable atau Sequence, atau memeriksa atribut __iter__ tidak cukup.

  3. Jika suatu objek o hanya mengimplementasikan __getitem__, tapi tidak __iter__, iter(o)akan membangun iterator yang mencoba mengambil item dari o dengan indeks integer, mulai dari indeks 0. Iterator akan menangkap apa saja IndexError (tetapi tidak ada kesalahan lain) yang dimunculkan dan kemudian muncul StopIteration diri.

  4. Dalam arti yang paling umum, tidak ada cara untuk memeriksa apakah iterator kembali iter adalah waras selain untuk mencobanya.

  5. Jika suatu objek o mengimplementasikan __iter__, yang iter fungsi akan memastikan bahwa objek itu dikembalikan oleh __iter__ adalah iterator. Tidak ada pemeriksaan kewarasan jika suatu objek hanya mengimplementasikan __getitem__.

  6. __iter__ menang. Jika suatu objek o mengimplementasikan keduanya __iter__ dan __getitem__, iter(o) akan menelepon __iter__.

  7. Jika Anda ingin membuat objek Anda sendiri dapat diulang, selalu terapkan __iter__ metode.

for loop

Untuk mengikuti, Anda perlu memahami apa yang terjadi ketika Anda menggunakan for loop dengan Python. Jangan ragu untuk langsung melewati bagian berikutnya jika Anda sudah mengetahuinya.

Ketika Anda menggunakan for item in o untuk beberapa objek iterable o, Python memanggil iter(o) dan mengharapkan objek iterator sebagai nilai kembalian. Sebuah iterator adalah objek yang mengimplementasikan a __next__ (atau next dengan metode Python 2) dan sebuah __iter__ metode.

Dengan konvensi, yang __iter__ metode iterator harus mengembalikan objek itu sendiri (mis. return self). Python kemudian memanggil next pada iterator sampai StopIteration dibesarkan. 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 lebih dari satu 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 pemeriksaan yang tidak dapat diandalkan

Pertimbangkan kelas berikut:

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

Panggilan iter dengan sebuah instance dari BasicIterable akan mengembalikan iterator tanpa masalah karena BasicIterable mengimplementasikan __getitem__.

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

Namun, penting untuk dicatat bahwa b tidak memiliki __iter__ atribut dan tidak dianggap sebagai turunan dari Iterable atau Sequence:

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

Ini sebabnya Fluent Python oleh Luciano Ramalho merekomendasikan menelepon iter dan menangani potensinya TypeError sebagai cara paling akurat untuk memeriksa apakah suatu objek dapat diteruskan. Mengutip langsung dari buku:

Pada Python 3.4, cara paling akurat untuk memeriksa apakah suatu objek x apakah iterable adalah menelepon iter(x) dan menangani TypeError kecuali jika tidak. Ini lebih akurat daripada menggunakan isinstance(x, abc.Iterable) , karena iter(x) juga menganggap warisan __getitem__ metode, sedangkan Iterable ABC tidak.

Pada poin 3: Iterasi objek yang hanya menyediakan __getitem__, tapi tidak __iter__

Iterasi atas contoh dari BasicIterable bekerja seperti yang diharapkan: Python membangun sebuah iterator yang mencoba mengambil item berdasarkan indeks, mulai dari nol, hingga sebuah IndexError dibesarkan. Objek demo __getitem__ metode hanya mengembalikan item yang disediakan sebagai argumen untuk __getitem__(self, item) oleh iterator 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 memunculkan StopIteration ketika tidak dapat mengembalikan item berikutnya dan bahwa IndexError yang dibangkitkan untuk item == 3 ditangani secara internal. Inilah sebabnya mengapa looping over a BasicIterable dengan for loop berfungsi seperti yang diharapkan:

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

Berikut ini contoh lain untuk mendorong pulang konsep bagaimana iterator kembali itermencoba mengakses item berdasarkan indeks. WrappedDict tidak 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 ke __getitem__ didelegasikan ke dict.__getitem__ yang notasi braket persegi hanyalah 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: iter memeriksa iterator saat panggilan __iter__:

Kapan iter(o) dipanggil untuk suatu objek o, iter akan memastikan bahwa nilai pengembalian __iter__, jika metodenya hadir, adalah iterator. Ini berarti objek yang dikembalikan harus menerapkan __next__ (atau next dengan Python 2) dan __iter__. iter tidak dapat melakukan pemeriksaan kewarasan untuk objek yang hanya menyediakan __getitem__, karena tidak ada 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 FailIterIterable instance gagal segera, saat membangun iterator dari FailGetItemIterable berhasil, tetapi akan melempar 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 benda mengimplementasikan __iter__ dan __getitem__, iter akan menelepon __iter__. Pertimbangkan kelas berikut

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

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

dan keluaran saat mengulang sebuah instance:

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

Pada poin 7: kelas iterable Anda harus diterapkan __iter__

Anda mungkin bertanya pada diri sendiri mengapa sebagian besar rangkaian bawaan seperti list terapkan __iter__ metode kapan __getitem__ akan cukup.

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

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

Setelah semua, iterasi atas contoh kelas di atas, yang mendelegasikan panggilan ke __getitem__ untuk 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 penerapan khusus Anda harus diterapkan __iter__ adalah sebagai berikut:

  1. Jika Anda menerapkan __iter__, contoh akan dianggap iterables, dan isinstance(o, collections.Iterable) akan kembali True.
  2. Jika objek itu dikembalikan oleh __iter__ bukan iterator, iter akan segera gagal dan membesarkan TypeError.
  3. Penanganan khusus __getitem__ ada untuk alasan kompatibilitas mundur. Mengutip kembali dari Fluent Python:

Itu sebabnya urutan Python apa pun iterable: mereka semua mengimplementasikan __getitem__ . Faktanya,   urutan standar juga berlaku __iter__, dan Anda juga harus, karena   penanganan khusus __getitem__ ada alasan kompatibilitas mundur dan mungkin   pergi di masa depan (meskipun tidak ditinggalkan saat saya menulis ini).


55
2018-04-04 16:04



Ini tidak cukup: objek dikembalikan oleh __iter__ harus menerapkan protokol iterasi (yaitu next metode). Lihat bagian yang relevan di dokumentasi.

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


28
2017-12-23 12:17



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

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


18
2017-12-23 12:21



Solusi terbaik yang saya temukan sejauh ini:

hasattr(obj, '__contains__')

yang pada dasarnya memeriksa apakah objek mengimplementasikan in operator.

Keuntungan (tidak ada solusi lain yang memiliki ketiga):

  • itu adalah ekspresi (berfungsi sebagai a lambda, Berbeda dengan coba ... kecuali varian)
  • itu (seharusnya) dilaksanakan oleh semua iterables, termasuk string (sebagai lawan __iter__)
  • bekerja pada sembarang Python> = 2.5

Catatan:

  • filosofi Python "minta maaf, bukan izin" tidak berfungsi dengan baik ketika mis. dalam daftar Anda memiliki keduanya iterables dan non-iterables dan Anda perlu memperlakukan setiap elemen secara berbeda sesuai dengan jenisnya (memperlakukan iterables pada try dan non-iterables pada kecuali akan bekerja, tetapi akan terlihat butt-jelek dan menyesatkan)
  • solusi untuk masalah ini yang mencoba untuk benar-benar mengulang objek (misalnya [x untuk x dalam obj]) untuk memeriksa apakah iterable dapat menyebabkan penalti kinerja yang signifikan untuk iterables besar (terutama jika Anda hanya memerlukan beberapa elemen pertama dari iterable, untuk contoh) dan harus dihindari

16
2017-11-09 16:40



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

Tapi karena Python 2.6 dan 3.0 Anda dapat memanfaatkan infrastruktur ABC (kelas basis abstrak) yang baru bersama dengan beberapa ABC builtin 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, hanya masalah konvensi. Seperti yang Anda lihat, Anda bisa mendaftarkan objek yang tidak dapat diubah sebagai Iterable - dan itu akan meningkatkan pengecualian pada saat runtime. Oleh karena itu, isinstance memperoleh arti "baru" - itu hanya memeriksa kompatibilitas "dinyatakan" jenis, yang merupakan cara yang baik untuk masuk 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 hanya membuang TypeError, tetapi jika ABC yang tepat telah terdaftar, cek Anda tidak berguna. Sebaliknya, jika __iter__ metode tersedia Python akan secara otomatis mengenali objek dari kelas itu sebagai Iterable.

Jadi, jika Anda hanya mengharapkan sebuah iterable, iterate di atasnya dan lupakan saja. Di sisi lain, jika Anda perlu melakukan hal yang berbeda tergantung pada jenis input, Anda mungkin menemukan infrastruktur ABC cukup bermanfaat.


14
2017-12-23 13:35



Saya menemukan solusi yang bagus sini:

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

11
2017-12-23 13:13



Anda dapat mencoba ini:

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

Jika kita bisa membuat generator yang mengulanginya (tetapi tidak pernah menggunakan generator sehingga tidak memakan ruang), itu bisa diteruskan. Sepertinya semacam "duh". Mengapa Anda perlu menentukan apakah suatu variabel dapat diulang sejak awal?


10
2017-12-23 12:21



Menurut Python 2 Glosarium, iterables adalah

semua jenis urutan (seperti list, str, dan tuple) dan beberapa jenis non-urutan seperti dict dan file dan objek dari setiap kelas yang Anda tentukan dengan __iter__() atau __getitem__() metode. Iterables dapat digunakan untuk loop dan di banyak tempat lain di mana urutan diperlukan (zip (), peta (), ...). Ketika sebuah objek iterable dilewatkan sebagai argumen ke fungsi built-in iter (), ia mengembalikan sebuah iterator untuk objek tersebut.

Tentu saja, diberikan gaya pengkodean umum untuk Python berdasarkan fakta bahwa itu "Lebih mudah untuk meminta maaf daripada izin.", Harapan umum adalah untuk 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 punya __iter__ metode (setidaknya tidak dengan Python 2, dengan Python 3 yang mereka lakukan) dan karena generator benda tidak memiliki __getitem__ metode.


7
2018-05-19 10:17



Saya sering menemukan nyaman, di dalam skrip saya, untuk mendefinisikan iterable fungsi. (Sekarang menggabungkan penyederhanaan yang disarankan Alfe):

import collections

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

sehingga Anda dapat menguji apakah ada objek yang dapat diulangi 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 cukup melakukan: from numpy import iterable, yang hanya sesuatu seperti

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

Jika Anda tidak memiliki numpy, Anda cukup mengimplementasikan kode ini, atau yang di atas.


4
2018-03-24 12:41