""" Урок 56. Генераторы — весь код примеров одним файлом. Источник: subjects/python-fundamentals/course/lessons/56-generators/examples.html Файл собран автоматически (tools/build_lesson_examples.py): правьте страницу урока. Запуск: python lesson-56.py """ # ==================================================================== # Пример 1. Вызов не выполняет тело функции # ==================================================================== def simple_generator(): print("Старт") yield 1 print("Середина") yield 2 print("Финиш") gen = simple_generator() print(gen) # print(type(gen)) # print(next(gen)) # Старт # 1 print(next(gen)) # Середина # 2 try: print(next(gen)) except StopIteration: print("StopIteration") # Финиш # StopIteration # ==================================================================== # Пример 2. next() со значением по умолчанию # ==================================================================== def limited(): yield "a" yield "b" g = limited() print(next(g)) # a print(next(g)) # b print(next(g, "конец данных")) # конец данных print(next(g, "конец данных")) # конец данных — можно звать сколько угодно раз # ==================================================================== # Пример 3. Генератор одноразовый # ==================================================================== def my_range(start, end): current = start while current < end: yield current current += 1 generator = my_range(1, 4) print(list(generator)) # [1, 2, 3] print(list(generator)) # [] — уже исчерпан print(list(my_range(1, 4))) # [1, 2, 3] — новый вызов = новый генератор # ==================================================================== # Пример 4. Зачем вообще ленивость: генератор против списка по памяти # ==================================================================== import sys list_comp = [x * x for x in range(1000)] gen_expr = (x * x for x in range(1000)) print(sys.getsizeof(list_comp)) # 8856 print(sys.getsizeof(gen_expr)) # 208 print(sum(list_comp)) # 332833500 print(sum(gen_expr)) # 332833500 — тот же результат # ==================================================================== # Пример 5. Бесконечный генератор и управляемая остановка # ==================================================================== from itertools import islice def infinite_counter(start=1): while True: yield start start += 1 counter = infinite_counter() print(list(islice(counter, 5))) # [1, 2, 3, 4, 5] print(next(counter)) # 6 — счётчик не сбросился, продолжает с того же места # ==================================================================== # Пример 6. close() завершает генератор досрочно # ==================================================================== def sensor_reader(values): for value in values: yield value sensor = sensor_reader([10, 20, 30, 40, 50]) collected = [] for value in sensor: collected.append(value) if value >= 30: sensor.close() break print(collected) # [10, 20, 30] try: next(sensor) except StopIteration: print("StopIteration after close") # StopIteration after close # ==================================================================== # Пример 7. yield from: делегирование и рекурсивный «расплющиватель» # ==================================================================== def flatten(nested): for item in nested: if isinstance(item, list): yield from flatten(item) else: yield item print(list(flatten([1, [2, [3, 4]], 5]))) # [1, 2, 3, 4, 5] def letters(): yield from "AB" # yield from работает с любым iterable, не только с генератором yield from [1, 2] print(list(letters())) # ['A', 'B', 1, 2] # ==================================================================== # Пример 8. send(): значения можно передавать внутрь генератора # ==================================================================== def running_total(): total = 0 while True: value = yield total total += value acc = running_total() print(next(acc)) # 0 — запускаем генератор до первого yield print(acc.send(10)) # 10 print(acc.send(5)) # 15 print(acc.send(-3)) # 12 # ==================================================================== # Пример 9. Генератор против класса-итератора: тот же результат, меньше кода # ==================================================================== class RangeIterator: def __init__(self, start, end): self.current = start self.end = end def __iter__(self): return self def __next__(self): if self.current >= self.end: raise StopIteration value = self.current self.current += 1 return value print(list(RangeIterator(1, 4))) # [1, 2, 3] print(list(my_range(1, 4))) # [1, 2, 3] — тот же результат, 6 строк вместо 11 # ==================================================================== # Пример 10. Конвейер из нескольких генераторов # ==================================================================== def read_lines(text): for line in text.splitlines(): yield line def non_empty(lines): for line in lines: stripped = line.strip() if stripped: yield stripped def numbered(lines): for index, line in enumerate(lines, start=1): yield f"{index}: {line}" report = """Alice: 90 Bob: 75 Carol: 88""" pipeline = numbered(non_empty(read_lines(report))) for row in pipeline: print(row) # 1: Alice: 90 # 2: Bob: 75 # 3: Carol: 88 # ==================================================================== # Пример 11. Ловушка: один и тот же генератор нельзя скормить двум потребителям # ==================================================================== def make_range(): return my_range(1, 4) shared = my_range(1, 4) first_pass = list(shared) second_pass = list(shared) print(first_pass, second_pass) # [1, 2, 3] [] fresh_first = list(make_range()) fresh_second = list(make_range()) print(fresh_first, fresh_second) # [1, 2, 3] [1, 2, 3]