Yanıtlar:
Python'daki yineleme nesneleri yineleyici protokolüne uygundur, bu da temel olarak iki yöntem sağladıkları anlamına gelir: __iter__()
ve __next__()
.
__iter__
Yineleyici nesnesi döndüren ve örtük döngüler başında denir.
__next__()
Yöntem aşağıdaki değerini verir ve dolaylı her döngü artan çağrılır. Bu yöntem, döndürülecek başka değer olmadığında bir StopIteration istisnası oluşturur; bu, yinelemeleri durdurmak için yapıları döngülerek örtülü olarak yakalanır.
Basit bir sayaç örneği:
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)
Bu yazdırılacak:
3
4
5
6
7
8
Önceki bir yanıtta anlatıldığı gibi, bir jeneratör kullanarak yazmak daha kolaydır:
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
Yazdırılan çıktı aynı olacaktır. Kaputun altında, jeneratör nesnesi yineleyici protokolünü destekler ve Counter sayacına kabaca benzer bir şey yapar.
David Mertz'in makalesi, Yineleyiciler ve Basit Jeneratörler , oldukça iyi bir tanıtım.
__next__
. counter
bir yineleyicidir, ancak bir dizi değildir. Değerlerini saklamaz. Örneğin, sayacı iki kez iç içe yerleştirilmiş bir döngü içinde kullanmamalısınız.
__iter__
( in'e ek olarak __init__
) atanmalıdır . Aksi takdirde, nesne yalnızca bir kez yinelenebilir. Örneğin, eğer derseniz ctr = Counters(3, 8)
, for c in ctr
birden fazla kullanamazsınız .
Counter
yineleyicidir ve yineleyicilerin yalnızca bir kez yinelenmesi gerekir. Sıfırlamak Eğer self.current
içinde __iter__
, daha sonra üzerinde iç içe bir döngü Counter
tamamen kırılmış olur ve Yineleyicilerin kabul davranışları (sesleniyor her türlü iter
üzerlerinde İdempotent olan) ihlal ediliyor. Bir ctr
kereden fazla yineleme yapabilmek istiyorsanız, yinelenemeyen yinelenebilir olması gerekir, burada her __iter__
çağrıldığında yepyeni bir yineleyici döndürür . Karıştırmaya ve eşleştirmeye çalışmak ( __iter__
çağrıldığında dolaylı olarak sıfırlanan bir yineleyici ) protokolleri ihlal eder.
Counter
yineleyici olmayan bir yinelemeli olsaydı, __next__
/ next
tamamen tanımını kaldıracak ve muhtemelen __iter__
bu cevabın sonunda açıklanan sınırla ( jeneratörün dışında) oluşturucu ile aynı formdaki bir jeneratör fonksiyonu olarak yeniden tanımlayacaksınız. argümanlar gelen etmek __iter__
için, onlar argümanlar olurdu __init__
kaydedilmiş self
gelen ve erişilen self
içinde __iter__
).
Yinelemeli bir işlev oluşturmanın dört yolu vardır:
__iter__
ve__next__
(veya next
Python 2.x'te))__getitem__
)Örnekler:
# 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]
Dört yöntemin de çalıştığını görmek için:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print(ch, end=' ')
print()
Sonuç:
A B C D E
A B C D E
A B C D E
A B C D E
Not :
İki jeneratör türü ( uc_gen
ve uc_genexp
) olamaz reversed()
; düz yineleyici ( uc_iter
) __reversed__
sihirli yönteme ( dokümanlara göre yeni bir yineleyici döndürmeli, ancak geri dönmelidir)self
işler (en azından CPython'da)); ve getitem iteratable ( uc_getitem
) __len__
yönteminin sihirli yöntemi olması gerekir :
# 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)
Albay Panic'in sonsuz tembel olarak değerlendirilmiş bir yineleyici hakkındaki ikincil sorusunu cevaplamak için, yukarıdaki dört yöntemin her birini kullanan örnekler:
# 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
Hangi sonuçlar (en azından örnek çalışmam için):
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
Hangisini kullanacağımı nasıl seçebilirim? Bu çoğunlukla bir tat meselesidir. En sık gördüğüm iki yöntem jeneratörler ve yineleyici protokolünün yanı sıra bir melezdir ( __iter__
bir jeneratör döndürür).
Jeneratör ifadeleri, liste kavrayışlarını değiştirmek için yararlıdır (tembeldir ve böylece kaynaklardan tasarruf edebilirler).
Daha önceki Python 2.x sürümleriyle uyumluluk gerekiyorsa kullanın __getitem__
.
uc_iter
süresi dolmalıdır (aksi takdirde sonsuz olarak); tekrar yapmak isterseniz, tekrar arayarak yeni bir yineleyici almanız gerekir uc_iter()
.
self.index = 0
içinde __iter__
size defalarca yineleme böylece. Aksi halde yapamazsınız.
Her şeyden önce itertools modülü , bir yineleyicinin yararlı olacağı her türlü durum için inanılmaz derecede yararlıdır, ancak işte python'da bir yineleyici oluşturmanız gerekir:
Yol ver
Güzel değil mi? Verim, bir işlevdeki normal bir dönüşün yerine kullanılabilir . Nesneyi aynı şekilde döndürür, ancak durumu yok etmek ve çıkmak yerine, bir sonraki yinelemeyi yürütmek istediğinizde durumdan tasarruf sağlar. Doğrudan itertools işlev listesinden alınan bir eylem örneği :
def count(n=0):
while True:
yield n
n += 1
İşlev açıklamasında belirtildiği gibi ( itertools modülünden count () işlevidir ...), n ile başlayan ardışık tamsayıları döndüren bir yineleyici üretir.
Jeneratör ifadeleri bir başka solucan kutusu (müthiş solucanlar!). Hafızadan tasarruf etmek için Liste Kavraması yerine kullanılabilirler (liste kavrayışları, bir değişkene atanmazsa kullanımdan sonra yok edilen bellekte bir liste oluşturur, ancak jeneratör ifadeleri, bir süslü yol olan bir Jeneratör Nesnesi oluşturabilir ... Yineleyici diyor). İşte bir jeneratör ifadesi tanımına bir örnek:
gen = (n for n in xrange(0,11))
Bu, tam aralığın 0 ile 10 arasında önceden belirlenmiş olması dışında, yukarıdaki yineleyici tanımımıza çok benzer.
Ben sadece xrange () buldum ( daha önce görmemiştim şaşırttı ...) ve yukarıdaki örneğe ekledi. xrange () , listenin önceden oluşturulmaması avantajına sahip range () öğesinin yinelenebilir bir sürümüdür . Eğer yinelemek için dev bir veri topluluğunuz varsa ve bunu yapmak için çok fazla belleğiniz olsaydı çok yararlı olurdu.
Bazılarınızın yaptığını görüyorum return self
içinde __iter__
. Sadece __iter__
kendisinin bir jeneratör olabileceğini belirtmek istedim (böylece ihtiyacı ortadan kaldırmak __next__
ve StopIteration
istisnaları arttırmak )
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
Tabii ki burada kişi doğrudan bir jeneratör de yapabilir, ancak daha karmaşık sınıflar için yararlı olabilir.
return self
içinde __iter__
. Ben yield
bunu kullanarak denemek için gidiyordu ben tam olarak ne denemek istiyorum yapıyor kodunuzu buldum.
next()
? return iter(self).next()
?
self.current
veya başka bir sayacı takip etmek zorunda değildir . Bu en çok oy alan cevap olmalı!
iter
, ancak bunlar sınıfın örnekleri değildir.
Bu soru yineleyicilerle değil, yinelenebilir nesnelerle ilgilidir. Python'da, sekanslar da tekrarlanabilir bir sınıftır, bu nedenle tekrarlanabilir bir sınıf oluşturmanın bir yolu, bir sekans gibi davranmasını sağlamak, yani onu __getitem__
ve __len__
yöntemlerini vermektir . Bunu Python 2 ve 3'te test ettim.
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__()
yöntemi olması gerekmez . __getitem__
yalnız beklenen davranış ile yeterlidir.
Bu sayfadaki tüm cevaplar karmaşık bir nesne için gerçekten harika. Fakat nitelikleri olarak iterable türleri yerleşik içerenler için, gibi str
, list
, set
veya dict
, ya herhangi uygulanması collections.Iterable
, kendi sınıfında bazı şeyleri atlayabilirsiniz.
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)
Gibi kullanılabilir:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
return iter(self.string)
.
Bu, yinelenmeyen bir işlevdir yield
. Bu iter
işlevi ve list
python 2 için kapalı kapsamda bir mutable ( ) durumunu tutan bir kapatma kullanır .
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)
Python 3 için, kapatma durumu çevreleme kapsamında değişmez bir şekilde tutulur nonlocal
ve durum değişkenini güncellemek için yerel kapsamda kullanılır.
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)
Ölçek;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
iter
, ama sadece açık olmak gerekirse: Bu, sadece yield
temel bir jeneratör işlevi kullanmaktan daha karmaşık ve daha az verimlidir ; Python, yield
burada yararlanamayacağınız temel jeneratör işlevleri için tonlarca tercüman desteğine sahiptir ve bu kodu önemli ölçüde yavaşlatır. Yine de yukarı oy kullandı.
Kısa ve basit bir şey arıyorsanız, belki de sizin için yeterli olacaktır:
class A(object):
def __init__(self, l):
self.data = l
def __iter__(self):
return iter(self.data)
kullanım örneği:
In [3]: a = A([2,3,4])
In [4]: [i for i in a]
Out[4]: [2, 3, 4]
Matt Gregory'nin cevabından esinlenerek, a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz döndürecek biraz daha karmaşık yineleyici
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)