Перейти до змісту

Ітератори

Щоб об'єкт став ітерабельним, з нього треба побудувати ще один спеціальний об'єкт, який називається ітератор. Для певного ітерабельного об'єкта за допомогою ітератора ми будемо отримувати значення з цього ітерабельного об'єкта. Ітератор як би створює потік даних, які містяться у ітерабельному об'єкті.

Ітератор є "одноразовим": якщо ми знову захочемо "пробігтись" по ітерабельному об'єкту, то нам треба буде отримати новий ітератор для цього ітерабельлного об'єкта.

Отримати ітератор для певного об'єкта можна за допомогою функції iter(). Ця функція робить наступне:

  1. Перевіряє, чи містить переданий ії об'єкт магічний метод __iter__(). Якщо так, то вона викликає цей магічний метод, а він у свою чергу повинен повернути об`єкт-ітератор.
  2. Якщо ж такого магічного метода немає, тоді функція iter() очікує що переданий їй об'єкт має магічний метод __getitem__(). У такому випадку Python побудує і створить ітератор самостійно.

Отже, щоб об'єкт був ітерабельним, він повинен коректно реалізовувати один з двох або ж обидва магічні методи: __iter__() та __getitem__(). При таких умовах Python може побудувати ітератор для даного об'єкта. І, відповідно, такий об'єкт ми можемо використовувати у циклі for, передавати функціям які очікують послідовності, тощо.

Давайте спробуємо отримати ітератори для деяких сутностей Python:

>>> i = iter('hello')
>>> i
<str_iterator object at 0x0000020BDD63F198>
>>> i = iter([1,3,5,7])
>>> i
<list_iterator object at 0x0000020BDD63F1D0>
>>> i = iter({})
>>> i
<dict_keyiterator object at 0x0000020BDD32A3B8>
>>> i = iter({}.values())
>>> i
<dict_valueiterator object at 0x0000020BDD306638>
>>> i = iter({}.items())
>>> i
<dict_itemiterator object at 0x0000020BDD32A3B8>
>>>

Отримання значень ітерабельних об'єктів

Щоб отримати від ітератора чергове значення ітерабельного об'єкта, починаючи з першого, використовують функцію next(). Ця функція у свою чергу викликає магічний метод ітератора __next__().

При кожному виклику метод __next__() повертає чергове значення з ітерабельного об'єкта, аж доки ці значення не закінчаться. Коли ж усі значення вичерпано, при черговому і кожному наступному виклику __next__() цей метод має викинути один з винятків — StopIteration або IndexError:

>>> i = iter('bye')
>>> next(i)
'b'
>>> next(i)
'y'
>>> next(i)
'e'
>>> next(i)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

Крім того кожен ітератор має реалізовувати магічний метод __iter__() (так само, як і сам ітерабельний об'єкт). Цей метод має повертати сам ітератор, зазвичай він виглядає так:

def __iter__(self):
    return self

Можна зробити висновок, що ітератор і сам є ітерабельним об'єктом і може бути використаним всюди, де очікуються ітерабельні об'єкти.

Отже циклом for ми можемо "пробігтись" як по самому ітерабельному об'єкту, так і по його ітератору. Але треба пам'ятати, що по самому ітерабельному об'єкту ми можемо ітеруватись багато раз, а по його ітератору лише один раз.

Як працює for

По усім вищенаведеним правилам працює і вже добре відомий нам цикл for:

  • створює для ітерабельного об'єкта ітератор
  • потім за допомогою ітератора отримує чергове значення для ітерабельного об'єкта до тих пір, доки не виникне виняток StopIteration

Ми навіть можемо самостійно реалізувати такий цикл:

>>> def my_for(iterable, callback_func):
...     iterator = iter(iterable)
...     while True:
...             try:
...                     value = next(iterator)
...                     callback_func(value)
...             except StopIteration:
...                     break
...
>>> def loop_body(value):
...     print(value)
...
>>> my_for('bye', loop_body)
b
y
e
>>>

Просто, і ніякої магії! Така сама логіка реалізована для цикла for.

Back to top