Ітератори в Python: навіщо вони потрібні розробнику
Ітератори Python відіграють важливу роль при роботі з колекціями даних. Завдяки їм можна виконувати поелементну обробку послідовності, що значно покращує гнучкість коду. Використання ітераторів знижує споживання пам’яті під час роботи з великими датасетами.
Сьогодні ми дізнаємось, у яких ситуаціях варто застосовувати ітератори на конкретних прикладах коду. Але спочатку трохи теоретичної інформації.
Ітератор (iterator) — це об’єкт, що підтримує протокол ітерації та реалізує два методи:
Як бачите, ітератори допомагають нам отримувати дані та значення у пам’яті з колекції по одному елементу. Це дуже важливо, якщо ми маємо справу з великим масивом даних.
Не всі об’єкти в Python можуть вважатися ітераторами, але багато хто з них допускає ітерацію по відношенню до себе. Об’єктом, що ітерується, можна назвати будь-який об’єкт, з яким можуть використовуватися цикл for і метод __iter__(). Зазвичай це різні списки та їхні варіації.
Щоб виконати ітерацію, спочатку вам потрібна функція iter, щоб отримати ітератор. При поверненні ітератора його можна використовувати для створення послідовних елементів через функцію next(). Подивіться на фрагмент коду, наведений нижче:
my_list = [100, 200, 300] my_iter = iter(my_list) # Отримуємо ітератор print(next(my_iter)) # Виведення 100 print(next(my_iter)) # Виведення 200 print(next(my_iter)) # Виведення 300 # Кожен наступний виклик next() стане причиною появи StopIteration
При відповіді на запитання про необхідність ітераторів у мові програмування Python можна згадати щонайменше чотири пункти:
Ітератори допомагають заощаджувати пам’ять, оскільки при їх застосуванні обробка даних відбувається поелементно, тобто вони не завантажуються все в пам’ять. Уявімо ситуацію, в якій нам необхідно згенерувати велику кількість даних з кількох мільйонів об’єктів. Якщо використовувати списки для зберігання всіх цих об’єктів, то нам знадобиться надзвичайно багато пам’яті. А ось ітератори дають нам можливість зберігати лише один елемент.
Перед вами наочний приклад функції, де числа повертаються по черзі (генератор):
def number_generator(n): i = 0 while i < n: yield i i += 1 for number in number_generator(1000000): print(number) # Виведення чисел від 0 до 999999
Функція yield тут працює в тому ж вигляді, що і ітератор: зупиняє виконання функції та повертає елемент. Під час наступного виклику вона продовжить виконання з того місця, де раніше зупинилася.
Ліниві обчислення передбачають генерацію або обробку даних тільки в тих ситуаціях, коли вони справді необхідні. Завдяки їм можна значно зменшити витрати ресурсів, що важливо у випадках, коли весь обсяг даних близький до нескінченності. Перед нами фрагмент коду, який демонструє суть лінивих обчислень:
def even_numbers(): num = 0 while True: yield num num += 2 evens = even_numbers() print(next(evens)) # 0 print(next(evens)) # 2 print(next(evens)) # 4
У цьому прикладі генератор виконує нескінченне повернення парних чисел. Але обчислення кожного числа відбувається лише під час виклику методу next().
Використовувати ітератори особливо корисно для обробки великих файлів. З ними додатку не потрібно вантажити в свою пам’ять цілий файл — його можна обробляти по кожному рядку. Розглянемо поданий нижче фрагмент коду:
with open('large_file.txt') as file: for line in file: process(line) # Виклик функції для почергової обробки кожного рядка
Тут файл є об’єктом, що ітерується, кожен його рядок можна обробити рядково, до наступного елемента в послідовності, без завантаження в пам’ять всього файлу.
У Python можна створювати кастомні (особливі) ітератори. Вони можуть стати в нагоді, якщо стандартні колекції не здатні задовольнити якісь спеціальні потреби в обробці об’єкта, що ітерується. Тут нам потрібен клас, який реалізує методи __iter__() та __next__(). У даному прикладі ми створимо клас Countdown, він оброблятиме кожен об’єкт, що ітерується, у зворотному порядку чисел, із зазначеного значення до одиниці.
class Countdown: def __init__(self, start): self.current = start def __iter__(self): return self def __next__(self): if self.current > 0: num = self.current self.current -= 1 return num else: raise StopIteration # Використання кастомного ітератора countdown = Countdown(5) for number in countdown: print(number)
Тепер слід пояснити, що ми тут бачимо:
При виведенні отримуємо
5 4 3 2 1
Тобто нам вдалося створити ітератор для подальшого використання в циклі for та інших конструкціях, що допускають ітерації.
Генератори — це особливий різновид ітераторів, які створюються при застосуванні ключового слова yield. З ними ви можете помітно спростити створення ітераторів, тому що тут не потрібна явна реалізація методів __iter__() та __next__().
Погляньмо на приклад, у якому генеруються числа від одиниці до нескінченності:
def infinite_numbers(): num = 1 while True: yield num num += 1 # Приклад застосування infinite_gen = infinite_numbers() # Друк перших 5 чисел із нескінченного генератора for _ in range(5): print(next(infinite_gen))
Пояснення:
При виведенні отримуємо перші 5 чисел, з одиниці та вище.
Крім генераторів, синтаксис Python має кілька інших рішень, які спрощують застосування ітераторів. Наприклад, вбудовані функції, такі як map(), filter(), zip(), та enumerate().
numbers = [10, 20, 30, 40, 50] # Збільшуємо кожне число на 1 incremented = list(map(lambda x: x + 1, numbers)) print(incremented) # Виведення: [20, 30, 40, 50, 60] # Фільтруємо тільки парні числа evens = list(filter(lambda x: x % 2 == 0, numbers)) print(evens) # Виведення: [20, 40]
Також можна використовувати comprehensions (генератори списків, кортежів, множин). Вони роблять код більш лаконічним. Ось конкретний приклад такого коду:
squared = [x**2 for x in range(10)] print(squared) # Виведення: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Ще один варіант роботи з ітераторами — це модуль itertools зі стандартної бібліотеки Python:
import itertools # Створюємо нескінченний ітератор infinite_count = itertools.count(start=10) # Виводимо перші 5 чисел for number in itertools.islice(infinite_count, 5): print(number)
Якщо ваш ітератор вимагає зберігання стану, тоді ви можете використовувати класи з атрибутами. Вони спрощують управління станом і робити код більш організованим.
class EvenNumbers: def __init__(self, max): self.current = 0 self.max = max def __iter__(self): return self def __next__(self): if self.current < self.max: result = self.current self.current += 2 return result else: raise StopIteration for num in EvenNumbers(10): print(num) # Виведення: 0, 2, 4, 6, 8
Як бачите, ітератори в Python мають цілу низку переваг, які роблять їх корисними для різних завдань при роботі з кодом. Вони заощаджують пам’ять, виконують операції тільки коли справді потрібна ітерація, обробляють дані при кожному виклику: частинами, а не всі відразу. Крім того, вони роблять код більш чистим і зручним для читання. Ітератори швидко масштабуються, це дозволяє створювати та обробляти кожен елемент у послідовності, незважаючи на її розміри.
CEO MacPaw Олександр Косован повідомив, що компанія вирішила призупинити роботу простору MacPaw Space у Києві.…
За словами Кріса Горна, директора фріланс-біржі Upwork, в Україні «зростає бізнес» з продажу особистих даних…
Нещодавнє оновлення віртуального помічника AI Assistant стало причиною конфлікту навколо оцінки цього програмного продукту з…
На конференції LlamaCon компанія Meta зробила кілька анонсів та представила інструменти, які мають зробити сімейство…
За даними аналітичної компанії Appfigures, з початку 2024 року кількість мобільних застосунків, розміщених на маркетплейсі…
CEO Microsoft Сатья Наделла заявив, що 20-30% коду в репозиторіях компанії написано програмним забезпеченням на…