Ітератори

Я почну з розгляду особливості мови Python, яка є важливою основою для написання програм в функціональному стилі: ітератори.

Ітератор — це об'єкт, що представляє потік даних; цей об'єкт повертає дані по одному елементу за раз. Ітератор в Python повинен підтримувати метод __next__(), який не приймає аргументів і завжди повертає наступний елемент потоку. Якщо в потоці більше немає елементів, __next__() повинен викликати виняток StopIteration. Ітератори не обов'язково повинні бути кінцевими; цілком розумно написати ітератор, який генерує нескінченний потік даних.

Вбудована функція iter() приймає довільний об'єкт і намагається повернути ітератор, який поверне вміст або елементи об'єкта, викликаючи TypeError, якщо об'єкт не підтримує ітерацію. Декілька вбудованих типів даних Python підтримують ітерацію, найпоширенішими з них є списки і словники. Об'єкт називається iterable, якщо для нього можна отримати ітератор.

Ви можете експериментувати з інтерфейсом ітерації вручну:

L = [1, 2, 3]
it = iter(L)
print(it)               # <...iterator object at ...>
print(it.__next__())    # Теж саме що й next(it)
print(next(it))
print(next(it))
print(next(it))         # Помилка StopIteration

Python очікує ітерабельні об'єкти в кількох різних контекстах, найважливішим з яких є оператор for. У виразі for X in Y, Y має бути ітератором або об'єктом, для якого iter() може створити ітератор. Ці два вирази еквівалентні:

obj = [1, 2, 3]

for i in iter(obj):
    print(i)

for i in obj:
    print(i)

Ітератори можуть бути матеріалізовані як списки або кортежі за допомогою конструкторських функцій list або tuple:

L = [1, 2, 3]
iterator = iter(L)
t = tuple(iterator)
t

Розпакування послідовностей також підтримує ітератори: якщо ви знаєте, що ітератор поверне N елементів, ви можете розпакувати їх у N-кортеж:

L = [1, 2, 3]
iterator = iter(L)
a, b, c = iterator
a, b, c

Вбудовані функції, такі як max і min, можуть приймати один аргумент-ітератор і повернути найбільший або найменший елемент. Оператори "in" і "not in" також підтримують ітератори: X in iterator є істинним, якщо X знайдено в потоці, що повертається ітератором. Ви зіткнетеся з очевидними проблемами, якщо ітератор нескінченний; max, min ніколи не повернуть значення, і якщо елемент X ніколи не з'явиться в потоці, оператори "in" і "not in" також не повернуть значення.

Зверніть увагу, що ви можете рухатися вперед в ітераторі; немає способу отримати попередній елемент, скинути ітератор або зробити його копію. Об'єкти-ітератори можуть опціонально надавати ці додаткові можливості, але протокол ітератора визначає лише метод ~iterator.__next__. Отже, функції можуть споживати весь вихід ітератора, і якщо вам потрібно зробити щось інше з тим же потоком, вам доведеться створити новий ітератор.

Типи даних, що підтримують ітератори

Ми вже бачили, як списки і кортежі підтримують ітератори. Насправді, будь-який тип послідовності в Python, такий як рядки, автоматично підтримує створення ітератора.

Виклик iter() на словнику повертає ітератор, який буде проходити по ключах словника:

m = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
     'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
for key in m:
    print(key, m[key])

Зверніть увагу, що починаючи з Python 3.7, порядок ітерації по словнику гарантовано буде таким самим, як і порядок вставки. У більш ранніх версіях поведінка була невизначеною і могла відрізнятися між реалізаціями.

Застосування iter() до словника завжди циклічно проходить по ключах, але словники мають методи, які повертають інші ітератори. Якщо ви хочете ітерувати по значеннях або парах ключ/значення, ви можете явно викликати методи dict.values() або dict.items() для отримання відповідного ітератора.

Конструктор dict може приймати ітератор, який повертає кінцевий потік кортежів (ключ, значення):

L = [('Italy', 'Rome'), ('France', 'Paris'), ('US', 'Washington DC')]
dict(iter(L))

Файли також підтримують ітерацію, викликаючи метод io.TextIOBase.readline() до тих пір, поки у файлі більше не залишиться рядків. Це означає, що ви можете читати кожен рядок файлу ось так:

for line in file:
    # робити щось для кожного рядка
    ...

Множини можуть отримувати свій вміст з ітерабельного об'єкта і дозволяти вам ітерувати по елементах множини:

S = {2, 3, 5, 7, 11, 13}
for i in S:
    print(i)

Текст на цій сторінці є перекладом "Functional Programming HOWTO", автор: A. M. Kuchling. Інформація про копірайт: History and License.