Die bou van'n Basiese Python Iterator
Vra
Hoe sou'n mens skep'n iteratiewe funksie (of iterator voorwerp) in python?
Oplossing
Iterator voorwerpe in python voldoen aan die iterator protokol, wat basies beteken dat hulle verskaf twee metodes: __iter__()
en next()
. Die __iter__
gee die iterator voorwerp en is implisiet genoem aan die begin van loops. Die next()
metode gee die volgende waarde en is implisiet genoem by elke lus inkrement. next()
gooi 'n StopIteration uitsondering wanneer daar is geen waarde meer om terug te keer, wat implisiet is gevang deur herhaling konstrukte te iterating stop.
Hier is 'n eenvoudige voorbeeld van 'n toonbank:
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def next(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
for c in Counter(3, 8):
print c
Dit wil druk:
3
4
5
6
7
8
Dit is makliker om te skryf met behulp van 'n kragopwekker, soos behandel in 'n vorige antwoord:
def counter(low, high):
current = low
while current <= high:
yield current
current += 1
for c in counter(3, 8):
print c
Die gedrukte uitset sal dieselfde wees. Onder die enjinkap, die kragopwekker voorwerp ondersteun die iterator protokol en doen iets rofweg ooreen met die klas Counter.
David Mertz se artikel, Iterators en Simple Generators , is 'n baie goeie inleiding.
Ander wenke
Daar is vier maniere om te bou'n iteratiewe funksie:
- skep'n kragopwekker (maak gebruik van die opbrengs navraag)
- gebruik'n kragopwekker uitdrukking (genexp)
- skep'n iterator (definieer
__iter__
en__next__
(ofnext
in Python 2.x)) - skep'n klas wat Python kan itereer oor op sy eie (definieer
__getitem__
)
Voorbeelde:
# generator
def uc_gen(text):
for char in text:
yield char.upper()
# generator expression
def uc_genexp(text):
return (char.upper() for char in text)
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index].upper()
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text
def __getitem__(self, index):
result = self.text[index].upper()
return result
Om te sien al vier metodes in aksie:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print ch,
print
Wat resultate in:
A B C D E
A B C D E
A B C D E
A B C D E
Nota:
Die twee generator tipes (uc_gen
en uc_genexp
) kan wees reversed()
;die vlakte iterator (uc_iter
) nodig sou wees om die __reversed__
magic metode (wat moet terugkeer'n nuwe iterator wat gaan agteruit);en die getitem iteratable (uc_getitem
) moet die __len__
magic metode:
# for uc_iter
def __reversed__(self):
return reversed(self.text)
# for uc_getitem
def __len__(self)
return len(self.text)
Om te antwoord Kolonel Paniek se sekondêre vraag oor'n oneindige lui geëvalueer iterator, hier is daardie voorbeelde, met behulp van elk van die vier bogenoemde metodes:
# 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
Wat die resultate in (ten minste vir my monster run):
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
In die eerste plek die itertools module is ongelooflik nuttig vir alle vorme van gevalle waar 'n iterator nuttig sou wees, maar hier is alles wat jy nodig het om 'n iterator in python skep:
opbrengs
Is dit nie koel? Opbrengs kan gebruik word om 'n normale terugkeer in 'n funksie te vervang. Dit gee die voorwerp net dieselfde, maar in plaas van die vernietiging van die staat en opwindende, dit spaar die staat vir wanneer jy wil die volgende iterasie te voer. Hier is 'n voorbeeld van dit in aksie direk getrek uit die itertools funksie lys :
def count(n=0):
while True:
yield n
n += 1
Soos in die funksies beskrywing (dis die telling () funksie van die itertools module ...), dit produseer 'n iterator dat opeenvolgende heelgetalle terug begin met n.
Generator uitdrukkings is 'n hele ander blikkie wurms (ongelooflike wurms!). Hulle kan gebruik word in plaas van 'n Lys Begrip om geheue te red (lys begripstoetse skep 'n lys in die geheue wat vernietig na gebruik indien nie aan 'n veranderlike, maar kragopwekker uitdrukkings kan 'n generator Object ... wat is 'n fancy manier om te sê Iterator) te skep. Hier is 'n voorbeeld van 'n definisie kragopwekker uitdrukking:
gen = (n for n in xrange(0,11))
Dit is baie soortgelyk aan ons iterator definisie hierbo, behalwe die volle omvang word elke jaar vooraf te wees tussen 0 en 10.
Ek het net gevind xrange () (verbaas ek dit nie vantevore gesien het nie ...) en dit saam met die bogenoemde voorbeeld. xrange () is 'n iterable weergawe van reeks () wat die voordeel van nie prebuilding die lys het. Dit sou baie nuttig wees as jy 'n reuse-korpus van data om oor herhaal het en het net soveel geheue om dit te doen in.
Ek sien sommige van julle doen return self
in __iter__
. Ek wou net om te sien dat __iter__
self kan 'n kragopwekker te wees (dus die verwydering van die behoefte aan __next__
en die verhoging van StopIteration
uitsonderings)
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
Natuurlik hier een kan net so goed direk 'n kragopwekker te maak, maar vir meer komplekse klasse kan dit nuttig wees.
Hierdie vraag is oor iterable voorwerpe, nie oor iterators. In Python, rye is iterable te so 'n manier om te maak 'n iterable klas is om dit te laat optree soos 'n ry, dit wil sê dit gee __getitem__
en __len__
metodes. Ek het hierdie toets op Python 2 en 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)
Dit is 'n iterable funksie sonder yield
. Dit maak gebruik van die iter
funksie en 'n afsluiting wat dit se toestand hou in 'n wispelturig (list
) in die omringende ruimte vir luislang 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)
Vir Python 3, sluiting staat gehou word in 'n onveranderlike in die omringende ruimte en nonlocal
gebruik in plaaslike omvang van die staat veranderlike op te dateer.
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)
Toets
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
Alle antwoorde op hierdie bladsy is werklik 'n groot vir 'n komplekse voorwerp. Maar vir diegene wat ingeboude iterable tipes as eienskappe, soos str
, list
, set
of dict
, of enige implementering van collections.Iterable
, kan jy sekere dinge laat in jou klas.
class Test(object):
def __init__(self, string):
self.string = string
def __iter__(self):
# since your string is already iterable
return (ch for ch in string)
Dit kan gebruik word soos:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
As jy op soek na iets kort en eenvoudig, miskien sal dit genoeg wees vir jou:
class A(object):
def __init__(self, l):
self.data = l
def __iter__(self):
return iter(self.data)
voorbeeld van die gebruik:
In [3]: a = A([2,3,4])
In [4]: [i for i in a]
Out[4]: [2, 3, 4]
geïnspireer deur antwoord Matt Gregory se hier is 'n bietjie meer ingewikkeld iterator dat a, b, ..., z, aa, ab, ..., zz, aaa, AAB, ..., zzy, zzz sal terugkeer
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)