Jawaban:
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 berkata ctr = Counters(3, 8)
, maka Anda tidak dapat menggunakan for c in ctr
lebih dari sekali.
Counter
adalah iterator, dan iterator hanya seharusnya diulang satu kali. Jika Anda me-reset self.current
di __iter__
, maka loop bersarang di atas Counter
akan benar-benar rusak, dan segala macam perilaku diasumsikan dari iterator (yang menyebut iter
mereka adalah idempoten) dilanggar. Jika Anda ingin dapat mengulangi ctr
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__
disimpan self
dan diakses dari self
dalam __iter__
).
Ada empat cara untuk membangun fungsi berulang:
__iter__
dan__next__
(atau next
dengan Python 2.x))__getitem__
)Contoh:
# generator
def uc_gen(text):
for char in text.upper():
yield char
# generator expression
def uc_genexp(text):
return (char for char in text.upper())
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text.upper()
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text.upper()
def __getitem__(self, index):
return self.text[index]
Untuk melihat keempat metode dalam aksi:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print(ch, end=' ')
print()
Yang mengakibatkan:
A B C D E
A B C D E
A B C D E
A B C D E
Catatan :
Dua tipe generator ( uc_gen
dan uc_genexp
) tidak bisa reversed()
; iterator polos ( uc_iter
) akan membutuhkan __reversed__
metode ajaib (yang, menurut dokumen , harus mengembalikan iterator baru, tetapi mengembalikan self
karya (setidaknya dalam CPython)); dan getitem iteratable ( uc_getitem
) harus memiliki __len__
metode ajaib:
# for uc_iter we add __reversed__ and update __next__
def __reversed__(self):
self.index = -1
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += -1 if self.index < 0 else +1
return result
# for uc_getitem
def __len__(self)
return len(self.text)
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:
# generator
def even_gen():
result = 0
while True:
yield result
result += 2
# generator expression
def even_genexp():
return (num for num in even_gen()) # or even_iter or even_getitem
# not much value under these circumstances
# iterator protocol
class even_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
# getitem method
class even_getitem():
def __getitem__(self, index):
return index * 2
import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
limit = random.randint(15, 30)
count = 0
for even in iterator():
print even,
count += 1
if count >= limit:
break
print
Yang menghasilkan (setidaknya untuk menjalankan sampel saya):
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
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__
.
uc_iter
harus kedaluwarsa saat selesai (jika tidak, akan tanpa batas); jika Anda ingin melakukannya lagi, Anda harus mendapatkan iterator baru dengan menelepon uc_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:
menghasilkan
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 :
def count(n=0):
while True:
yield n
n += 1
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:
gen = (n for n in xrange(0,11))
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.
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 meningkatkan StopIteration
pengecualian)
class range:
def __init__(self,a,b):
self.a = a
self.b = b
def __iter__(self):
i = self.a
while i < self.b:
yield i
i+=1
Tentu saja di sini orang mungkin juga secara langsung membuat generator, tetapi untuk kelas yang lebih kompleks dapat bermanfaat.
return self
di __iter__
. Ketika saya akan mencoba menggunakan yield
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.
class CustomRange:
def __init__(self, low, high):
self.low = low
self.high = high
def __getitem__(self, item):
if item >= len(self):
raise IndexError("CustomRange index out of range")
return self.low + item
def __len__(self):
return self.high - self.low
cr = CustomRange(0, 10)
for i in cr:
print(i)
__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
atau dict
, atau pelaksanaan collections.Iterable
, Anda dapat menghilangkan hal-hal tertentu di kelas Anda.
class Test(object):
def __init__(self, string):
self.string = string
def __iter__(self):
# since your string is already iterable
return (ch for ch in self.string)
# or simply
return self.string.__iter__()
# also
return iter(self.string)
Dapat digunakan seperti:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
return iter(self.string)
.
Ini adalah fungsi yang dapat diubah tanpa yield
. Itu menggunakan iter
fungsi dan penutupan yang membuat keadaan itu bisa berubah ( list
) dalam lingkup melampirkan untuk python 2.
def count(low, high):
counter = [0]
def tmp():
val = low + counter[0]
if val < high:
counter[0] += 1
return val
return None
return iter(tmp, None)
Untuk Python 3, status penutupan disimpan dalam kekekalan dalam lingkup melampirkan dan nonlocal
digunakan dalam lingkup lokal untuk memperbarui variabel status.
def count(low, high):
counter = 0
def tmp():
nonlocal counter
val = low + counter
if val < high:
counter += 1
return val
return None
return iter(tmp, None)
Uji;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
iter
, tetapi hanya untuk memperjelas: Ini lebih kompleks dan kurang efisien daripada hanya menggunakan yield
fungsi generator berbasis; Python memiliki banyak dukungan juru bahasa untuk yield
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:
class A(object):
def __init__(self, l):
self.data = l
def __iter__(self):
return iter(self.data)
contoh penggunaan:
In [3]: a = A([2,3,4])
In [4]: [i for i in a]
Out[4]: [2, 3, 4]
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
class AlphaCounter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
alpha = ' abcdefghijklmnopqrstuvwxyz'
n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
if n_current > n_high:
raise StopIteration
else:
increment = True
ret = ''
for x in self.current[::-1]:
if 'z' == x:
if increment:
ret += 'a'
else:
ret += 'z'
else:
if increment:
ret += alpha[alpha.find(x)+1]
increment = False
else:
ret += x
if increment:
ret += 'a'
tmp = self.current
self.current = ret[::-1]
return tmp
for c in AlphaCounter('a', 'zzz'):
print(c)