Серия Python — часть 20

Генераторы — это мощная функция Python, которая позволяет нам создавать итерируемые объекты эффективным и удобным для памяти способом. Они обеспечивают элегантное решение для генерации последовательностей значений на лету, без сохранения их всех в памяти. В этом промежуточном уроке мы погрузимся в мир генераторов и изучим их возможности. К концу этой статьи у вас будет четкое представление о генераторах и о том, как использовать их возможности в ваших проектах Python.

Понимание генераторов

Генераторы — это тип итерируемых объектов, которые генерируют значения на лету, а не сохраняют их все в памяти сразу. Они обеспечивают эффективный способ работы с большими наборами данных или бесконечными последовательностями. Вместо использования традиционных структур данных, таких как списки или массивы, генераторы создают значения динамически, используя оператор yield. Это делает их удобными для памяти и позволяет обрабатывать большие наборы данных с минимальным потреблением памяти.

Создание генераторов

Функции генератора

Генераторные функции определяются как обычные функции, но производят последовательность. Когда вызывается функция-генератор, она возвращает объект-итератор, который может выполнять итерацию по сгенерированным значениям. Каждый раз, когда встречается оператор yield, состояние функции сохраняется, а полученное значение возвращается вызывающей стороне. Функция может быть возобновлена ​​с того места, где она была остановлена ​​на следующей итерации.

Давайте рассмотрим пример функции-генератора, которая генерирует последовательность четных чисел:

def even_numbers(n):
    for i in range(n):
        if i % 2 == 0:
            yield i

В этом примере функция генератора even_numbers принимает число n в качестве входных данных и генерирует все четные числа до n. Мы используем оператор yield для получения каждого четного числа по одному.

Чтобы использовать генератор, мы можем перебрать его с помощью цикла или явным вызовом функции next() на итераторе:

for num in even_numbers(10):
    print(num)

Выход:

0
2
4
6
8

Выражения генератора

Выражения генератора обеспечивают краткий способ создания генераторов без явного определения функции генератора. У них такой же синтаксис, как и у списков, но они создают генератор вместо создания списка. Выражения генератора заключены в круглые скобки и могут использоваться непосредственно в местах, где ожидается итерация.

Давайте изменим предыдущий пример, используя выражение генератора:

even_nums = (i for i in range(10) if i % 2 == 0)

В этом случае мы создаем генераторное выражение, которое генерирует четные числа от 0 до 9. Условие i % 2 == 0 отфильтровывает нечетные числа. Мы можем перебирать выражение генератора так же, как и раньше:

for num in even_nums:
    print(num)

Выход:

0
2
4
6
8

Преимущество использования генераторных выражений заключается в том, что они более эффективно используют память, чем создание списков, особенно при работе с большими наборами данных.

Перебор генераторов

Поскольку генераторы являются итерируемыми объектами, их можно быстро итерировать, используя циклы или передавая их функциям, которые принимают итерируемые объекты. Значения генерируются по запросу, что означает, что за один раз создается и сохраняется в памяти только одно значение. Это особенно полезно при работе с большими наборами данных, которые не помещаются полностью в память.

Давайте рассмотрим пример, где мы генерируем числа Фибоначчи с помощью генератора:

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()

for _ in range(10):
    print(next(fib))

Выход:

0
1
1
2
3
5
8
13
21
34

В этом примере функция генератора fibonacci создает бесконечную последовательность чисел Фибоначчи. Мы можем использовать функцию next(), чтобы получить следующее значение из генератора и распечатать его. Поскольку последовательность Фибоначчи бесконечна, мы используем range(10), чтобы ограничить вывод первыми 10 числами.

Понимание генератора

Подобно генераторам списков, генераторы обеспечивают краткий способ создания генераторов. У них тот же синтаксис, что и у списков, но вместо квадратных скобок они используют круглые скобки. Генераторные включения предлагают более экономичный способ создания последовательностей значений, чем создание списков.

Давайте создадим генератор, который генерирует квадраты чисел от 1 до 5:

squares = (x ** 2 for x in range(1, 6))

for num in squares:
    print(num)

Выход:

1
4
9
16
25

В этом примере понимание генератора (x ** 2 for x in range(1, 6)) генерирует квадрат каждого числа от 1 до 5. Затем мы можем перебрать генератор и напечатать каждый квадрат.

Цепочки и конвейерные генераторы

Генераторы можно комбинировать и связывать вместе для выполнения сложных задач обработки данных. Подключение нескольких генераторов с помощью оператора yield позволяет создавать мощные конвейеры данных, которые преобразуют и фильтруют данные на лету. Это позволяет использовать эффективный и модульный код для обработки больших наборов данных без сохранения промежуточных результатов в памяти.

Давайте посмотрим на пример цепочки генераторов для фильтрации и преобразования последовательности чисел:

def squares(n):
    for i in range(n):
        yield i ** 2

def even_numbers(seq):
    for num in seq:
        if num % 2 == 0:
            yield num

numbers = [1, 2, 3, 4, 5]
pipeline = even_numbers(squares(5))

for num in pipeline:
    print(num)

Выход:

0
4

В этом примере мы определяем две функции генератора: squares и even_numbers. Генератор squares производит квадрат каждого числа от 0 до n. Генератор even_numbers отфильтровывает нечетные числа из заданной последовательности. Мы связываем эти генераторы вместе, передавая вывод squares(5) в even_numbers. Результирующий конвейер генератора генерирует только четные квадраты чисел от 0 до 4.

Усовершенствованные методы генератора

Отправка значений генераторам

Помимо генерации значений, генераторы также могут получать значения от вызывающего объекта, используя метод генератора send(). Это обеспечивает двустороннюю связь между генератором и вызывающей стороной, позволяя передавать значения туда и обратно во время итерации. Эту функцию можно использовать для создания более динамичных и интерактивных рабочих процессов на основе генератора.

Давайте изменим пример Фибоначчи, чтобы вызывающая сторона могла отправлять значение, которое сбрасывает последовательность:

def fibonacci():
    a, b = 0, 1
    while True:
        reset = yield a
        if reset:
            a, b = 0, 1
        else:
            a, b = b, a + b

fib = fibonacci()

print(next(fib))  # Output: 0
print(next(fib))  # Output: 1
print(next(fib))  # Output: 1

print(fib.send(True))  # Output: 0
print(next(fib))       # Output: 1
print(next(fib))       # Output: 1

В этом модифицированном примере Фибоначчи мы используем оператор yield как для создания значения (a), так и для получения значения (reset) от вызывающего объекта. Если полученное значение равно True, мы сбрасываем последовательность Фибоначчи в исходное состояние. Это позволяет вызывающей стороне динамически управлять поведением генератора.

Обработка исключений в генераторах

Генераторы предоставляют встроенные механизмы обработки исключений, которые позволяют перехватывать и обрабатывать исключения внутри генератора. Используя блок try-except внутри функции генератора, вы можете изящно обрабатывать ошибки и продолжать итерацию.

Давайте рассмотрим пример, где мы генерируем простые числа с помощью генератора:

def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

def prime_numbers():
    n = 2
    while True:
        try:
            if is_prime(n):
                yield n
            n += 1
        except StopIteration:
            break

primes = prime_numbers()

for _ in range(5):
    print(next(primes))

Выход:

2
3
5
7
11

В этом примере функция is_prime проверяет, является ли число простым. Генератор prime_numbers непрерывно генерирует простые числа, увеличивая n и получая простые числа. Мы обрабатываем исключение StopIteration внутри генератора, чтобы изящно остановить итерацию, когда это необходимо.

Заключение

Генераторы — это мощный инструмент в Python, который может значительно повысить эффективность и гибкость вашего кода. Вы можете перебирать большие наборы данных, используя генераторы, создавать последовательности с эффективным использованием памяти и создавать сложные конвейеры обработки данных. Понимание того, как создавать, перебирать и комбинировать генераторы, откроет новые возможности в ваших проектах программирования.

LinkedIn, Medium, Instagram, Kaggle и GitHub.

Если вам нравится читать истории, подобные этой, и вы хотите поддержать меня, подумайте о том, чтобы стать участником Medium. Взяв 5 долларов в месяц, вы открываете неограниченный доступ к историям на Medium. Если вы воспользуетесь моей ссылкой для регистрации, я получу небольшую комиссию.

Уже участник? Подпишитесь, чтобы получать уведомления, когда я опубликую.

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .