Учебное пособие по разработке функций генератора 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 ++.
Я считаю, что эта статья поможет вам в будущем улучшать программное обеспечение и исследовательские программы. Спасибо за прочтение.
Ура 😃