Vra

Hoe sou'n mens skep'n iteratiewe funksie (of iterator voorwerp) in python?

Was dit nuttig?

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:

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)
Gelisensieer onder: CC-BY-SA met toeskrywing
Nie verbonde aan StackOverflow
scroll top