Ітератори в Python: навіщо вони потрібні розробнику
Ітератори Python відіграють важливу роль при роботі з колекціями даних. Завдяки їм можна виконувати поелементну обробку послідовності, що значно покращує гнучкість коду. Використання ітераторів знижує споживання пам’яті під час роботи з великими датасетами.
Сьогодні ми дізнаємось, у яких ситуаціях варто застосовувати ітератори на конкретних прикладах коду. Але спочатку трохи теоретичної інформації.
Що таке ітератори?
Ітератор (iterator) — це об’єкт, що підтримує протокол ітерації та реалізує два методи:
- __iter__(). Якщо ви викликаєте iter() на об’єкті або використовуєте його за допомогою циклу for, Python хоче викликати метод __iter__() цього об’єкта. Якщо об’єкт ітерабельний, то він повертає ітератор.
- __next__(). Цей метод використовується для повернення наступного елемента з послідовності під час роботи з ітераторами. Щоразу, коли викликається метод __next__() на ітераторі, він має повертати наступний елемент. Якщо, після обходу елементів, їх для повернення вже немає, метод повертає виключення StopIteration, щоб повідомити про закінчення ітерації.
Як бачите, ітератори допомагають нам отримувати дані та значення у пам’яті з колекції по одному елементу. Це дуже важливо, якщо ми маємо справу з великим масивом даних.
Ітеровані об’єкти та ітератори
Не всі об’єкти в 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 можна згадати щонайменше чотири пункти:
- Економія пам’яті.
- Ліниві обчислення (Lazy Evaluation).
- Обробка файлів та потоків даних.
- Створення кастомних ітераторів.
Економія пам’яті
Ітератори допомагають заощаджувати пам’ять, оскільки при їх застосуванні обробка даних відбувається поелементно, тобто вони не завантажуються все в пам’ять. Уявімо ситуацію, в якій нам необхідно згенерувати велику кількість даних з кількох мільйонів об’єктів. Якщо використовувати списки для зберігання всіх цих об’єктів, то нам знадобиться надзвичайно багато пам’яті. А ось ітератори дають нам можливість зберігати лише один елемент.
Перед вами наочний приклад функції, де числа повертаються по черзі (генератор):
def number_generator(n): i = 0 while i < n: yield i i += 1 for number in number_generator(1000000): print(number) # Виведення чисел від 0 до 999999
Функція yield тут працює в тому ж вигляді, що і ітератор: зупиняє виконання функції та повертає елемент. Під час наступного виклику вона продовжить виконання з того місця, де раніше зупинилася.
Ліниві обчислення (Lazy Evaluation)
Ліниві обчислення передбачають генерацію або обробку даних тільки в тих ситуаціях, коли вони справді необхідні. Завдяки їм можна значно зменшити витрати ресурсів, що важливо у випадках, коли весь обсяг даних близький до нескінченності. Перед нами фрагмент коду, який демонструє суть лінивих обчислень:
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)
Тепер слід пояснити, що ми тут бачимо:
- У класі Countdown конструктор __init__ набуває початкового значення start і зберігає його в атрибуті current.
- У методі __iter__ повертається сам об’єкт, що робить його ітератором.
- Метод __next__ перевіряє, чи більше значення 0. Якщо так, то повертає поточне значення і зменшує його на 1.
- Якщо значення менше або 0, викидає виняток StopIteration, що повідомляє про закінчення ітерації.
При виведенні отримуємо
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))
Пояснення:
- while True: Цей цикл робить генератор нескінченним.
- yield num: Функція повертає поточне значення num, але не завершує виконання, дозволяючи продовжити з цього місця на наступному дзвінку.
- next(): Вручну викликає наступну ітерацію генератора.
При виведенні отримуємо перші 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 мають цілу низку переваг, які роблять їх корисними для різних завдань при роботі з кодом. Вони заощаджують пам’ять, виконують операції тільки коли справді потрібна ітерація, обробляють дані при кожному виклику: частинами, а не всі відразу. Крім того, вони роблять код більш чистим і зручним для читання. Ітератори швидко масштабуються, це дозволяє створювати та обробляти кожен елемент у послідовності, незважаючи на її розміри.
Favbet Tech – це ІТ-компанія зі 100% українською ДНК, що створює досконалі сервіси для iGaming і Betting з використанням передових технологій та надає доступ до них. Favbet Tech розробляє інноваційне програмне забезпечення через складну багатокомпонентну платформу, яка здатна витримувати величезні навантаження та створювати унікальний досвід для гравців.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: