Учебное пособие по разработке функций генератора Python с использованием ключевого слова yield

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

Генератор выражений

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

>>> import sys
>>> a = [x for x in range(1000000)]
>>> b = (x for x in range(1000000))
>>> sys.getsizeof(a)
8697472
>>> sys.getsizeof(b)
128
>>> a
[0, 1, ... 999999]
>>> b
<generator object <genexpr> at 0x1020de6d0>

Мы видим, что в приведенном выше сценарии мы экономим довольно много памяти, используя генератор вместо массива.

Функции с доходностью вместо возврата

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

def isPrime(n):
    if n < 2 or n % 1 > 0:
        return False
    elif n == 2 or n == 3:
        return True
    for x in range(2, int(n**0.5) + 1):
        if n % x == 0:
            return False
    return True
def getPrimes():
    value = 0
    while True:
        if isPrime(value):
            yield value
        value += 1

Как вы можете видеть во второй функции, мы выполняем итерацию в цикле while и получаем простые числа. Давайте посмотрим, как мы можем использовать указанный выше генератор.

primes = getPrimes()
>>> next(primes)
2
>>> next(primes)
3
>>> next(primes)
5

Сначала мы вызываем функцию и получаем экземпляр генератора. Хотя это может имитировать бесконечный массив, еще не найдено ни одного элемента. Если вы вызовете list(primes), ваша программа может аварийно завершить работу с ошибкой MemoryError. Однако для простых чисел он туда не попадет, поскольку пространство простых чисел разрежено для вычислений, позволяющих достичь предела памяти за конечное время. Однако для генераторов вы не будете знать заранее длину. Если вы позвоните len(primes), вы получите следующую ошибку по той же причине, что номера генерируются только во время выполнения.

----------------------------------------------------------------
TypeError                      Traceback (most recent call last)
<ipython-input-33-a6773446b45c> in <module>
----> 1 len(primes)

TypeError: object of type 'generator' has no len()

Генераторы с конечным числом итераций

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

Зачем нам нужно использовать yield?

Представьте, что размер файла - 1 ТБ, а набор слов - 500000. Он не поместится в памяти. Простое решение - прочитать 2 строки одновременно, вычислить словарь слов для каждой строки и вернуть его с семантической оценкой в ​​следующей строке. Файл будет выглядеть так, как показано ниже.

The product is well packed
5
Edges of the packaging was damaged and print was faded.
3
Avoid this product. Never going to buy anything from ShopX.
1
Shipping took a very long time
2

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

Реализация генератора синтаксического анализа файлов

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

def readData(path):
    with open(path) as f:
        sentiment = ""
        line = ""
        for n, d in enumerate(f):
            if n % 2 == 0:
                line = d.strip()
            else:
                sentiment = int(d.strip())
                yield line, sentiment

Мы можем использовать указанную выше функцию в for цикле следующим образом.

>>> data = readData("test.txt")
>>> for l, s in data: print(l, s)
The product is well packed 5
Edges of the packaging was damaged and print was faded. 3
Avoid this product. Never going to buy anything from ShopX. 1
Shipping took a very long time 2

Как выходит генератор?

В обычном цикле for итерация останавливается, когда генератор больше не выполняет уступку. Однако это можно наблюдать, если вручную вызвать next() в экземпляре генератора. Вызов next() за пределом итераций вызовет следующее исключение.

----------------------------------------------------------------
StopIteration                  Traceback (most recent call last)
<ipython-input-41-cddec6aa1599> in <module>
---> 28 print(next(data))
StopIteration:

Использование send, throw и close

функция отправки

Вспомним наш пример простых чисел. Представьте, что мы хотим сбросить значение нашей функции генератора на 100, чтобы начать выдавать значения выше 100, если они простые. Мы можем использовать метод send() в экземпляре генератора, чтобы передать значение в генератор, как показано ниже.

>>> primes = getPrimes()
>>> next(primes)
2
>>> primes.send(10)
11
>>> primes.send(100)
101

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

def getPrimes():
    value = 0
    while True:
        if isPrime(value):
            i = yield value
            if i is not None:
                value = i
        value += 1

Полученное значение мы сохраняем в переменной i. Если это не None тип, мы присваиваем его переменной value. None проверка важна, так как первая next() не будет иметь значения в value переменной, которую нужно передать.

функция выброса

Представьте, что вы хотите завершить итерацию при значении выше 10, чтобы избежать переполнения или тайм-аутов (гипотетически). Функцию throw() можно использовать, чтобы побудить генератор прекратить создание исключения.

primes = getPrimes()
for x in primes:
    if x > 10:
        primes.throw(ValueError, "Too large")
    print(x)

Этот метод полезен для проверки входных данных. Логика лежит на пользователе генератора. Это приведет к следующему выводу.

2
3
5
7
----------------------------------------------------------------
ValueError                     Traceback (most recent call last)
<ipython-input-113-37adca265503> in <module>
     12 for x in primes:
     13     if x > 10:
---> 14         primes.throw(ValueError, "Too large")
     15     print(x)

<ipython-input-113-37adca265503> in getPrimes()
      3     while True:
      4         if isPrime(value):
----> 5             i = yield value
      6             if i is not None:
      7                 value = i

ValueError: Too large

функция закрытия

Часто бывает элегантно держать крышку в руке без исключения. В таких сценариях функция theclose() может использоваться для эффективного закрытия итератора.

primes = getPrimes()
for x in primes:
    if x > 10:
        primes.close()
    print(x)

Это даст нам следующий результат.

2
3
5
7
11

Обратите внимание, что у нас есть значение 11, которое является последним вычисленным значением, превышающим 11. Это имитирует поведение цикла do while в C / C ++.

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

Ура 😃