Zbuduj podstawowy Iterator Pythona

Jak stworzyć funkcję iteracyjną (lub obiekt iteracyjny) w Pythonie?

Author: user2357112, 2008-08-21

9 answers

Obiekty iteratora w Pythonie są zgodne z protokołem iteratora, co oznacza, że dostarczają dwie metody: __iter__() i next(). __iter__ zwraca obiekt iterator i jest niejawnie wywoływany na początku pętli. Metoda next() zwraca następną wartość i jest domyślnie wywoływana przy każdym przyroście pętli. next() podnosi wyjątek Stopiteracji, gdy nie ma więcej wartości do zwrócenia, który jest domyślnie przechwytywany przez konstrukcje pętlowe, aby zatrzymać iterację.

Oto prosty przykład z licznika:

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

To wydrukuje:

3
4
5
6
7
8

Jest to łatwiejsze do napisania za pomocą generatora, co zostało omówione w poprzedniej odpowiedzi:

def counter(low, high):
    current = low
    while current <= high:
        yield current
        current += 1

for c in counter(3, 8):
    print c

Wydruk będzie taki sam. Pod maską obiekt generator obsługuje protokół iteratora i robi coś mniej więcej podobnego do licznika klas.

Artykuł Davida Mertza, Iteratory i proste Generatory, jest całkiem dobrym wprowadzeniem.

 543
Author: ars,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-13 12:40:26

Istnieją cztery sposoby budowania funkcji iteracyjnej:

Przykłady:

# 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

Aby zobaczyć wszystkie cztery metody w działaniu:

for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
    for ch in iterator('abcde'):
        print ch,
    print

Które wyniki w:

A B C D E
A B C D E
A B C D E
A B C D E

Uwaga:

Dwa typy generatorów (uc_gen i uc_genexp) nie mogą być reversed(); zwykły iterator (uc_iter) potrzebuje magicznej metody __reversed__ (która musi zwrócić nowy iterator, który idzie wstecz); a iterable getitem (uc_getitem) musi mieć magiczną metodę __len__:

    # for uc_iter
    def __reversed__(self):
        return reversed(self.text)

    # for uc_getitem
    def __len__(self)
        return len(self.text)

Aby odpowiedzieć na drugorzędne pytanie pułkownika Panic o nieskończony leniwie oceniany iterator, oto te przykłady, używając każdej z czterech metod powyżej:

# 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

Co daje (przynajmniej dla mojego przykładowego biegu):

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
 342
Author: Ethan Furman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-27 23:16:30

Po pierwsze moduł itertools jest niezwykle przydatny w różnych przypadkach, w których iterator byłby przydatny, ale oto wszystko, czego potrzebujesz, aby utworzyć iterator w Pythonie:

Yield

Czy to nie fajne? Wydajność może być użyta do zastąpienia normalnego zwrotu w funkcji. Zwraca obiekt tak samo, ale zamiast niszczyć stan i kończyć, zapisuje stan, kiedy chcesz wykonać następną iterację. Oto przykład tego w akcja pobrana bezpośrednio z listy funkcji itertools :
 def count(n=0):
     while True:
         yield n
         n += 1

Jak podano w opisie funkcji (jest to funkcja count () z modułu itertools...), tworzy iterator, który zwraca kolejne liczby całkowite zaczynające się od n.

Wyrażenia generatora to zupełnie inna puszka robaków (awesome worms!). Mogą być używane zamiast rozumienia Listy do zapisywania pamięci (składanie listy tworzy listę w pamięci, która jest destroyed after use if not assigned to a variable, but generator expressions can create a Generator Object... co jest fantazyjnym sposobem na powiedzenie Iterator). Oto przykład definicji wyrażenia generatora:

gen = (n for n in xrange(0,11))

Jest to bardzo podobne do powyższej definicji iteratora, z wyjątkiem tego, że pełny zakres jest z góry określony jako od 0 do 10.

Właśnie znalazłem xrange () (zdziwiony, że wcześniej go nie widziałem...) i dodał go do powyższego przykładu. xrange () jest iterowalną Wersja range () , która ma tę zaletę, że nie tworzy wstępnie listy. Byłoby bardzo przydatne, gdybyś miał gigantyczny korpus danych do iteracji i miał tylko tyle pamięci, aby to zrobić.

 99
Author: akdom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-22 00:21:13

Widzę, że niektórzy z was robią return self w __iter__. Chciałem tylko zauważyć, że __iter__ sama może być generatorem (eliminując w ten sposób potrzebę __next__ i podnosząc StopIteration wyjątki)

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

Oczywiście tutaj równie dobrze można zrobić generator bezpośrednio, ale dla bardziej złożonych klas może to być przydatne.

 85
Author: Manux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-27 15:05:12

To pytanie dotyczy obiektów iteracyjnych, a nie iteratorów. W Pythonie sekwencje są też iterowalne, więc jednym ze sposobów na stworzenie klasy iteraowalnej jest zachowanie jej jak sekwencji, tzn. nadanie jej metod __getitem__ i __len__. Przetestowałem to na Pythonie 2 i 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)
 8
Author: aq2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-21 17:39:14

Jest to funkcja iteracyjna Bez yield. Korzysta z funkcji iter i zamknięcia, które utrzymuje jego stan w mutowalnym (list) zakresie zamykającym dla Pythona 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)

W Pythonie 3 Stan zamknięcia jest utrzymywany w niezmiennym zakresie, a {[6] } jest używany w zakresie lokalnym do aktualizacji zmiennej stanu.

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)  

Test;

for i in count(1,10):
    print(i)
1
2
3
4
5
6
7
8
9
 3
Author: Nizam Mohamed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-03 17:55:26

Jeśli szukasz czegoś krótkiego i prostego, może to ci wystarczy:

class A(object):
    def __init__(self, l):
        self.data = l

    def __iter__(self):
        return iter(self.data)

Przykład użycia:

In [3]: a = A([2,3,4])

In [4]: [i for i in a]
Out[4]: [2, 3, 4]
 1
Author: Daniil Mashkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-26 08:38:39

Wszystkie odpowiedzi na tej stronie są naprawdę świetne dla złożonego obiektu. Ale dla tych, które zawierają wbudowane typy iteracyjne jako atrybuty, jak str, list, set lub dict, lub dowolna implementacja collections.Iterable, możesz pominąć pewne rzeczy w swojej klasie.

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)

Może być używany jak:

for x in Test("abcde"):
    print(x)

# prints
# a
# b
# c
# d
# e
 1
Author: John Strood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-03 10:02:21

Zainspirowany odpowiedzią Matta Gregory ' ego tutaj jest nieco bardziej skomplikowany iterator, który zwróci 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)
 0
Author: Ace.Di,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-13 17:34:13