Bagaimana cara membuat fungsi berulang (atau objek iterator) dengan python?
Objek Iterator di python sesuai dengan protokol iterator, yang pada dasarnya berarti mereka menyediakan dua metode: __iter__()
dan __next__()
.
The __iter__
mengembalikan objek iterator dan secara implisit disebut di awal loop.
The __next__()
Metode mengembalikan nilai berikutnya dan secara implisit disebut pada setiap kenaikan lingkaran. Metode ini memunculkan pengecualian StopIteration ketika tidak ada lagi nilai untuk kembali, yang secara implisit ditangkap oleh pengulangan konstruksi untuk menghentikan iterasi.
Ini contoh sederhana penghitung:
class Counter:
def __init__(self, low, high):
self.current = low - 1
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 2: def next(self)
self.current += 1
if self.current < self.high:
return self.current
raise StopIteration
for c in Counter(3, 9):
print(c)
Ini akan mencetak:
3
4
5
6
7
8
Ini lebih mudah untuk ditulis menggunakan generator, seperti yang tercakup dalam jawaban sebelumnya:
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
Output yang dicetak akan sama. Di bawah tenda, objek generator mendukung protokol iterator dan melakukan sesuatu yang kira-kira mirip dengan Counter kelas.
Artikel David Mertz, Iterators and Simple Generator , adalah pengantar yang cukup bagus.
__next__
.counter
adalah iterator, tapi itu bukan urutan. Itu tidak menyimpan nilainya. Anda tidak boleh menggunakan penghitung dalam loop berulang-kali, misalnya.__iter__
(selain ke dalam__init__
). Jika tidak, objek hanya dapat diulang satu kali. Misalnya, jika Anda berkatactr = Counters(3, 8)
, maka Anda tidak dapat menggunakanfor c in ctr
lebih dari sekali.Counter
adalah iterator, dan iterator hanya seharusnya diulang satu kali. Jika Anda me-resetself.current
di__iter__
, maka loop bersarang di atasCounter
akan benar-benar rusak, dan segala macam perilaku diasumsikan dari iterator (yang menyebutiter
mereka adalah idempoten) dilanggar. Jika Anda ingin dapat mengulangictr
lebih dari satu kali, itu harus non-iterator iterable, di mana ia mengembalikan iterator baru setiap kali__iter__
dipanggil. Mencoba untuk mencampur dan mencocokkan (sebuah iterator yang secara implisit direset ketika__iter__
dipanggil) melanggar protokol.Counter
menjadi non-iterator iterable, Anda akan menghapus definisi__next__
/next
seluruhnya, dan mungkin mendefinisikan ulang__iter__
sebagai fungsi generator dengan bentuk yang sama seperti generator yang dijelaskan pada akhir jawaban ini (kecuali alih-alih batas) datang dari argumen ke__iter__
, mereka akan argumen untuk__init__
disimpanself
dan diakses dariself
dalam__iter__
).Ada empat cara untuk membangun fungsi berulang:
__iter__
dan__next__
(ataunext
dengan Python 2.x))__getitem__
)Contoh:
Untuk melihat keempat metode dalam aksi:
Yang mengakibatkan:
Catatan :
Dua tipe generator (
uc_gen
danuc_genexp
) tidak bisareversed()
; iterator polos (uc_iter
) akan membutuhkan__reversed__
metode ajaib (yang, menurut dokumen , harus mengembalikan iterator baru, tetapi mengembalikanself
karya (setidaknya dalam CPython)); dan getitem iteratable (uc_getitem
) harus memiliki__len__
metode ajaib:Untuk menjawab pertanyaan sekunder Kolonel Panic tentang iterator malas yang dievaluasi tanpa batas, berikut adalah contoh-contohnya, menggunakan masing-masing dari empat metode di atas:
Yang menghasilkan (setidaknya untuk menjalankan sampel saya):
Bagaimana memilih yang mana yang akan digunakan? Ini sebagian besar masalah selera. Dua metode yang paling sering saya lihat adalah generator dan protokol iterator, serta hibrida (
__iter__
mengembalikan generator).Ekspresi generator berguna untuk mengganti pemahaman daftar (mereka malas sehingga dapat menghemat sumber daya).
Jika seseorang membutuhkan kompatibilitas dengan versi Python 2.x sebelumnya gunakan
__getitem__
.sumber
uc_iter
harus kedaluwarsa saat selesai (jika tidak, akan tanpa batas); jika Anda ingin melakukannya lagi, Anda harus mendapatkan iterator baru dengan meneleponuc_iter()
lagi.self.index = 0
di__iter__
sehingga Anda dapat iterate berkali-kali. Kalau tidak, Anda tidak bisa.Pertama-tama modul itertools sangat berguna untuk semua jenis kasus di mana iterator akan berguna, tetapi di sini adalah semua yang Anda butuhkan untuk membuat iterator dengan python:
Bukankah itu keren? Yield dapat digunakan untuk menggantikan normal kembali dalam suatu fungsi. Ini mengembalikan objek sama saja, tetapi alih-alih menghancurkan negara dan keluar, ia menyimpan keadaan ketika Anda ingin menjalankan iterasi berikutnya. Berikut ini adalah contoh tindakan yang diambil langsung dari daftar fungsi itertools :
Seperti yang dinyatakan dalam deskripsi fungsi (ini adalah fungsi count () dari modul itertools ...), ia menghasilkan iterator yang mengembalikan bilangan bulat berurutan dimulai dengan n.
Ekspresi generator adalah kaleng cacing lainnya (cacing luar biasa!). Mereka dapat digunakan sebagai pengganti Pemahaman Daftar untuk menghemat memori (pemahaman daftar membuat daftar dalam memori yang dihancurkan setelah digunakan jika tidak ditugaskan ke variabel, tetapi ekspresi generator dapat membuat Obyek Generator ... yang merupakan cara yang bagus untuk mengatakan Iterator). Berikut adalah contoh definisi ekspresi generator:
Ini sangat mirip dengan definisi iterator kami di atas kecuali rentang penuh telah ditentukan antara 0 dan 10.
Saya baru saja menemukan xrange () (kaget saya belum pernah melihatnya sebelumnya ...) dan menambahkannya ke contoh di atas. xrange () adalah versi rentang iterable () yang memiliki keuntungan tidak membuat ulang daftar. Akan sangat berguna jika Anda memiliki kumpulan data raksasa untuk diulangi dan hanya memiliki begitu banyak memori untuk melakukannya.
sumber
Aku melihat beberapa dari Anda lakukan
return self
di__iter__
. Saya hanya ingin mencatat bahwa__iter__
itu sendiri bisa menjadi generator (sehingga menghilangkan kebutuhan__next__
dan meningkatkanStopIteration
pengecualian)Tentu saja di sini orang mungkin juga secara langsung membuat generator, tetapi untuk kelas yang lebih kompleks dapat bermanfaat.
sumber
return self
di__iter__
. Ketika saya akan mencoba menggunakanyield
di dalamnya saya menemukan kode Anda melakukan persis apa yang ingin saya coba.next()
?return iter(self).next()
?self.current
atau counter lainnya. Ini harus menjadi jawaban terpilih!iter
instance kelas, tetapi mereka sendiri bukan instance kelas.Pertanyaan ini adalah tentang objek yang dapat diubah, bukan tentang iterator. Dalam Python, sekuens juga dapat diubah sehingga salah satu cara untuk membuat kelas iterable adalah membuatnya berperilaku seperti sekuens, yaitu memberikannya
__getitem__
dan__len__
metode. Saya telah menguji ini pada Python 2 dan 3.sumber
__len__()
metode.__getitem__
sendirian dengan perilaku yang diharapkan sudah cukup.Semua jawaban pada halaman ini sangat bagus untuk objek yang kompleks. Tetapi bagi mereka yang mengandung builtin jenis iterable sebagai atribut, seperti
str
,list
,set
ataudict
, atau pelaksanaancollections.Iterable
, Anda dapat menghilangkan hal-hal tertentu di kelas Anda.Dapat digunakan seperti:
sumber
return iter(self.string)
.Ini adalah fungsi yang dapat diubah tanpa
yield
. Itu menggunakaniter
fungsi dan penutupan yang membuat keadaan itu bisa berubah (list
) dalam lingkup melampirkan untuk python 2.Untuk Python 3, status penutupan disimpan dalam kekekalan dalam lingkup melampirkan dan
nonlocal
digunakan dalam lingkup lokal untuk memperbarui variabel status.Uji;
sumber
iter
, tetapi hanya untuk memperjelas: Ini lebih kompleks dan kurang efisien daripada hanya menggunakanyield
fungsi generator berbasis; Python memiliki banyak dukungan juru bahasa untukyield
fungsi-fungsi generator berbasis yang tidak dapat Anda manfaatkan di sini, membuat kode ini jauh lebih lambat. Tetap terpilih.Jika Anda mencari sesuatu yang pendek dan sederhana, mungkin itu sudah cukup untuk Anda:
contoh penggunaan:
sumber
Terinspirasi oleh jawaban Matt Gregory di sini adalah iterator yang sedikit lebih rumit yang akan mengembalikan a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz
sumber