AD0DE412
то уж разкажите про протокол next
Есть глобальные функции iter() и next(). Функция iter() создаёт итератор и возвращает его. А функция next() к существующему итератору применяет операцию “взять следующий элемент” и возвращает этот элемент полученный. Когда итератор заканчивается, функция next() порождает исключение StopIteration. Оно внутри итератора возникает, переходит функции next(), а функция next() его уже наружу проводит.
Так вот цикл for эту функцию iter() вызывает неявно, а потом он так же неявно вызывает функцию next() для неявно полученного итератора. Таким образом, если ты элементы удалил какие-то впереди, то все элементы впереди сдвинутся, а цикл for на следующем шаге вызовет так же эту функцию next() и возьмёт только элемент, который там следующий по очереди по расчётам итератора. Но итератор их просто считает и знает, какой элемент следующий по счёту; он не следит, как они там удаляются без его участия. Итератор, например, их посчитал “я уже три элемента видел и вернул, а следующий элемент четвёртый”. А ты берёшь и четвёртый удаляешь, а вместо четвёртого пятый элемент ставишь. А итератор всё так же считает “я вижу четвёртый элемент, я его возвращаю, следующий будет пятый элемент”. То есть итератор не понимает, что элементы поменялись в количестве, поэтому он думает, что пятый элемент - это четвёртый, что шестой элемент - это пятый.
Дальше, что такое протокол итератора. Функции iter() и next() обращаются к методам __iter__() и __next__(). Функция iter() ищет метод __iter__() у объекта, чтобы получить из этого метода итератор. А функция next() ищет метод __next__() у итератора, чтобы получить следующий элемент или исключение StopIteration.
Любой объект, который у себя реализовал метод __iter__(), становится итерируемым - то есть способным построить итератор по запросу из функции iter() и вернуть его.
Любой объект, который у себя реализовал метод __next__(), становится итератором - то есть способным взять элемент по запросу из функции next() и вернуть его.
Пример
>>> text = 'abc'
>>>
>>> text.__iter__
<method-wrapper '__iter__' of str object at 0x7facc4b00ae8>
>>>
>>> text.__next__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__next__'
>>>
>>> it = iter(text)
>>> it
<str_iterator object at 0x7facbc979710>
>>>
>>> it.__iter__
<method-wrapper '__iter__' of str_iterator object at 0x7facbc979710>
>>>
>>> it.__next__
<method-wrapper '__next__' of str_iterator object at 0x7facbc979710>
>>>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
>>> itit = iter(it)
>>> itit
<str_iterator object at 0x7facbc979710>
>>>
>>> itit.__iter__
<method-wrapper '__iter__' of str_iterator object at 0x7facbc979710>
>>>
>>> itit.__next__
<method-wrapper '__next__' of str_iterator object at 0x7facbc979710>
>>>
>>> next(itit)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
Как видим, у строки есть метод __iter__(), поэтому для неё можно построить итератор. При этом строка не является итератором сама по себе, потому что у неё нет метода __next__().
Когда мы построили итератор для строки, мы получили объект, у которого есть метод __next__(). Также у этого объекта есть метод __iter__(), чтобы для итератора можно было построить новый итератор. При этом, как показывает практика, обычно метод __iter__() у итератора возвращает ссылку на сам этот итератор, то есть итератор возвращает сам себя. Но это и необязательно, можно и что-то новое возвращать.
Пример
>>> class A:
... def __iter__(self):
... self.n = 0
... return self
... def __next__(self, default=None):
... if self.n < 3:
... self.n += 1
... return 'nothing'
... else:
... raise StopIteration
...
>>> a = A()
>>> a
<__main__.A object at 0x7fce57bf9748>
>>>
>>> it = iter(a)
>>> it
<__main__.A object at 0x7fce57bf9748>
>>>
>>> next(it)
'nothing'
>>> next(it)
'nothing'
>>> next(it)
'nothing'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 10, in __next__
StopIteration
>>>
>>> for i in A():
... print(i)
...
nothing
nothing
nothing
>>>