Разделение методов __iter__ и __next__ - PullRequest
0 голосов
/ 28 августа 2018

В Python 3 это стандартная процедура, позволяющая сделать класс итератором и итератором одновременно, определив методы __iter__ и __next__. Но у меня есть проблемы, чтобы обернуть голову вокруг этого. Возьмите этот пример, который создает итератор, который производит только четные числа:

class EvenNumbers:

    def __init__(self, max_):
        self.max_ = max_

    def __iter__(self):
        self.n = 0
        return self

    def __next__(self):
        if self.n <= self.max:
            result = 2 * self.n
            self.n += 1
            return result

        raise StopIteration

instance = EvenNumbers(4)

for entry in instance:
    print(entry)

Насколько мне известно (поправьте меня, если я ошибаюсь), когда я создаю цикл, создается итератор, вызывающий что-то вроде itr = iter(instance), которое внутренне вызывает метод __iter__. Ожидается, что это вернет объект итератора (который экземпляр должен определить __next__, и поэтому я могу просто вернуть себя). Чтобы получить из него элемент, вызывается next(itr) до тех пор, пока не будет сгенерировано исключение.

Мой вопрос здесь и сейчас: если и как __iter__ и __next__ можно разделить, чтобы содержимое последней функции было определено где-то еще? И когда это может быть полезно? Я знаю, что мне нужно изменить __iter__, чтобы он возвращал итератор.

Кстати, идея сделать это приходит с этого сайта ( LINK ), где не указано, как это реализовать.

Ответы [ 2 ]

0 голосов
/ 28 августа 2018

Я думаю, что я понял концепцию сейчас, даже если я не до конца понимаю отрывок из документации @FHTMitchell. Я натолкнулся на пример того, как разделить два метода, и хотел документировать это.

То, что я нашел, - очень базовый учебник , в котором четко проводится различие между итерируемым и итератором (что является причиной моей путаницы).

По сути, сначала вы определяете свою итерацию как отдельный класс:

class EvenNumbers:

    def __init__(self, max_):
        self.max = max_

    def __iter__(self):
        self.n = 0
        return EvenNumbersIterator(self)

Для метода __iter__ требуется только объект, для которого определен метод __next__. Поэтому вы можете сделать это:

class EvenNumbersIterator:

    def __init__(self, source):
        self.source = source       

    def __next__(self):
        if self.source.n <= self.source.max:
            result = 2 * self.source.n
            self.source.n += 1
            return result
        else:
            raise StopIteration

Это отделяет часть итератора от итерируемого класса. Теперь имеет смысл, что если я определю __next__ внутри итерируемого класса, мне придется вернуть ссылку на сам экземпляр, так как он в основном выполняет 2 задания одновременно.

0 голосов
/ 28 августа 2018

Похоже, вы путаете итераторы и итерируемые . Итераторы имеют метод __iter__, который возвращает итератор. Итераторы имеют метод __next__, который возвращает либо следующее значение, либо возвращает StopIteration. Теперь в python указано , что итераторы также являются итераторами (но не наоборот) и что iter(iterator) is iterator, поэтому итератор, itr, должен возвращать только себя из своего метода __iter__.

Итераторы должны иметь метод __iter__(), который возвращает сам объект итератора, поэтому каждый итератор также является итеративным и может использоваться в большинстве мест, где принимаются другие итерируемые элементы

В коде:

class MyIter:
   def __iter__(self):
       return self

   def __next__(self):
       # actual iterator logic

Если вы хотите создать собственный класс итератора, самый простой способ - наследовать от collections.abc.Iterator, который, как вы видите, определяет __iter__, как указано выше (это также подкласс collections.abc.Iterable) , Тогда все, что вам нужно, это

class MyIter(collections.abc.Iterator):
    def __next__(self):
        ...

Конечно, есть намного более простой способ сделать итератор, и это с функцией генератора

def fib():
    a = 1
    b = 1
    yield a
    yield b
    while True:
        b, a = a + b, b
        yield b

list(itertools.takewhile(lambda x: x < 100, fib()))
# --> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Просто для справки, это (упрощенный) код для абстрактного итератора и повторяемый

from abc import ABC, abstractmethod

class Iterable(ABC):

    @abstractmethod
    def __iter__(self):
        'Returns an instance of Iterator'
        pass

class Iterator(Iterable, ABC):

    @abstractmethod
    def __next__(self):
        'Return the next item from the iterator. When exhausted, raise StopIteration'
        pass

    # overloads Iterable.__iter__
    def __iter__(self):
        return self
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...