python итераторов, генераторов и между ними - PullRequest
1 голос
/ 02 марта 2020

Таким образом, я получаю функции генератора для ленивых вычислений и выражений генератора, также известные как генераторные выражения как его синтаксис c сахарный эквивалент.

Я понимаю такие классы, как

class Itertest1:
    def __init__(self):
        self.count = 0
        self.max_repeats = 100

    def __iter__(self):
        print("in __inter__()")
        return self

    def __next__(self):
        if self.count >= self.max_repeats:
            raise StopIteration
        self.count += 1
        print(self.count)
        return self.count

как способ реализация интерфейса итератора, то есть iter () и next () в одном и том же классе.

Но что тогда является

class Itertest2:
    def __init__(self):
        self.data = list(range(100))

    def __iter__(self):
        print("in __inter__()")
        for i, dp in enumerate(self.data):
            print("idx:", i)
            yield dp

, который использует оператор yield в функции-члене iter ?

Также я заметил, что при вызове iter функция-член

it = Itertest2().__iter__()
batch = it.__next__()

оператор print выполняется только при вызове next () в первый раз. Это из-за этой странной смеси урожайности и иттеров? Я думаю, что это довольно противоречиво ...

Ответы [ 2 ]

1 голос
/ 02 марта 2020

Что-то эквивалентное Itertest2 может быть написано с использованием отдельного класса итератора.

class Itertest3:
    def __init__(self):
        self.data = list(range(100))

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


class Itertest3Iterator:
    def __init__(self, data):
        self.data = enumerate(data)

    def __iter__(self):
        return self

    def __next__(self):
        print("in __inter__()")
        i, dp = next(self.state)  # Let StopIteration exception propagate
        print("idx:", i)
        return dp

Сравните это с Itertest1, где сам экземпляр Itertest1 переносил состояние итерации в Это. Каждый вызов Itertest1.__iter__ возвращал один и тот же объект (экземпляр Itertest1), поэтому они не могли перебирать данные независимо.

Обратите внимание, что я поместил print("in __iter__()") в __next__, а не __iter__. Как вы заметили, ничто в функции генератора на самом деле не выполняет до первого вызова __next__. Сама функция генератора only создает генератор; фактически он не начинает выполнять код в нем.

1 голос
/ 02 марта 2020

Наличие оператора yield в любом месте любой функции оборачивает код функции в (собственный) объект генератора и заменяет функцию на заглушку, которая дает вам указанный объект генератора.

Итак, вызов __iter__ предоставит вам анонимный объект генератора, который выполняет нужный код.

Основной вариант использования для __next__ - предоставить способ написания итератора без использования (собственных) генераторов.

Вариант использования __iter__ заключается в различении guish между объектом и состоянием итерации над указанным объектом. Рассмотрим код, подобный

c = some_iterable()
for a in c:
    for b in c:
        # do something with a and b

. Вы бы не хотели, чтобы две чередующиеся итерации вмешивались в состояние друг друга. Вот почему такой al oop будет десагарничать к чему-то вроде

c = some_iterable()
_iter1 = iter(c)
try:
    while True:
        a = next(_iter1)
        _iter2 = iter(c)
        try:
            while True:
                b = next(_iter2)
                # do something with a and b
        except StopIteration:
            pass
 except StopIteration:
     pass

. Как правило, пользовательские итераторы реализуют заглушку __iter__, которая возвращает self, так что iter(iter(x)) эквивалентно iter(x). Это важно при написании обёрток итератора.

...