Что делает yield python
Перейти к содержимому

Что делает yield python

  • автор:

Выражение/оператор yield в Python

Оператор yield семантически эквивалентен выражению yield . Понятия оператор yield и выражение yield отличаются наличием скобок. Оператор yield используется без скобок, которые требуются в эквивалентном выражении yield .

yield expression> yield from expression> # эквивалентно операторам выражения yield (yield expression>) (yield from expression>) 

Выражение yield используется при определении функции генератора или асинхронной функции генератора и, следовательно, может использоваться только в теле определения функции. Использование выражения yield в теле функции приводит к тому, что эта функция является генератором, а использование его в теле функции сопрограммы async def приводит к тому, что она становится асинхронным генератором.

# определяет функцию генератора def gen(): yield 123 # определяет функцию асинхронного генератора async def agen(): yield 123 

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

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

Как работает выражение yield в функциях генераторов можно узнать из разделов:

  • Функция генератора,
  • Преобразование простой функция в функцию-генератор,
  • Тип данных generator (генератор),
  • Асинхронный генератор в Python.
  • КРАТКИЙ ОБЗОР МАТЕРИАЛА.
  • Преобразование простой функции в генератор Python
  • Передача значений в генератор Python
  • Использование send(), throw() и close() в генераторах Python
  • Выражение yield
  • Выражение yield from
  • Выражение-генератор
  • Обработка больших данных при помощи генераторов Python

Ключевое слово yield в Python

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

Код #1: Демонстрация работы yield

 
# Код на Python3 для демонстрации # использования ключевого слова yield # генерация нового списка, состоящего # только из четных чисел def get_even(list_of_nums) : for i in list_of_nums: if i % 2 == 0: yield i # инициализация списка list_of_nums = [1, 2, 3, 8, 15, 42] # вывод начального списка print ("До фильтрации в генераторе: " + str(list_of_nums)) # вывод только четных значений из списка print ("Только четные числа: ", end = " ") for i in get_even(list_of_nums): print (i, end = " ")
 
До фильтрации в генераторе: [1, 2, 3, 8, 15, 42] Только четные числа: 2 8 42

Код #2

 
# Данная Python программа выводит # числа от 1 до 15, возведенные в куб, # используя yield и, следовательно, генератор # Функция ниже будет бесконечно генерировать # последовательность чисел в третьей степени, # начиная с 1 def nextCube(): acc = 1 # Бесконечный цикл while True: yield acc**3 acc += 1 # После повторного обращения # исполнение продолжится отсюда # Ниже мы запрашиваем у генератора # и выводим ровно 15 чисел count = 1 for num in nextCube(): if count > 15: break print(num) count += 1
 
1 8 27 64 125 216 343 512 729 1000 1331 1728 2197 2744 3375

Преимущества yield:

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

Недостатки yield:

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

Практическое применение

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

 
# Код Python3 для демонстрации # использования ключевого слова yield # Поиск слова pythonru в тексте # Импорт библиотеки для работы # с регулярными выражениями import re # Этот генератор создает последовательность # значений True: по одному на каждое # найденное слово pythonru # Также для наглядности он выводит # обработанные слова def get_pythonru (text) : text = re.split('[., ]+', text) for word in text: print(word) if word == "pythonru": yield True # Инициализация строки, содержащей текст для поиска text = "В Интернете есть множество сайтов, \ но только один pythonru. \ Программа никогда не прочтет \ последнее предложение." # Инициализация переменной с результатом result = "не найден" # Цикл произведет единственную итерацию # в случае наличия в тексте pythonru и # не сделает ни одной, если таких слов нет for j in get_pythonru(text): result = "найден" break print ('Результат поиска: %s' % result)

Сравнение операторов yield и return в Python (с примерами)

В этой статье мы расскажем про основные различия между yield и return в Python. А для лучшего понимания этих различий приведем пару примеров.

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

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

Return также является встроенным ключевым словом в Python. Он завершает функцию, а вызывающей стороне отправляет значение.

Разница между yield и return

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

return yield
Оператор return возвращает только одно значение. Оператор yield может возвращать серию результатов в виде объекта-генератора.
Return выходит из функции, а в случае цикла он закрывает цикл. Это последний оператор, который нужно разместить внутри функции. Не уничтожает локальные переменные функции. Выполнение программы приостанавливается, значение отправляется вызывающей стороне, после чего выполнение программы продолжается с последнего оператора yield.
Логически, функция должна иметь только один return. Внутри функции может быть более одного оператора yield.
Оператор return может выполняться только один раз. Оператор yield может выполняться несколько раз.
Return помещается внутри обычной функции Python. Оператор yield преобразует обычную функцию в функцию-генератор.

Пример 1

Теперь давайте рассмотрим разницу между операторами return и yield на примерах.

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

num1 =10 num2=20 def mathOP(): return num1+num2 return num1-num2 return num1*num2 return num1/num2 print(mathOP())

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

snimok

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

num1 =10 num2=20 def sumOP(): return num1+num2 def subtractOP(): return num1-num2 def multiplicationOP(): return num1*num2 def divisionOP(): return num1/num2 print("The sum value is: ",sumOP()) print("The difference value is: ",subtractOP()) print("The multiplication value is: ",multiplicationOP()) print("The division value is: ",divisionOP())

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

snimok1

Однако мы можем выполнить эти арифметические операции внутри одной функции-генератора, используя несколько операторов yield.

num1 =10 num2=20 def mathOP(): yield num1+num2 yield num1-num2 yield num1*num2 yield num1/num2 print("Printing the values:") for i in mathOP(): print(i)

snimok2

Пример 2

Давайте рассмотрим еще один пример использования операторов return и yield.

Создадим список чисел и передадим его в функцию mod() в качестве аргумента. Далее, внутри функции, мы проверяем каждый элемент списка. Если он делится без остатка на 10, то мы его выводим.

Для начала давайте реализуем этот пример в нашем скрипте Python с использованием оператора return.

myList=[10,20,25,30,35,40,50] def mod(myList): for i in myList: if(i%10==0): return i print(mod(myList))

Оператор return возвращает только первое число, кратное 10, и завершает выполнение функции.

snimok3

Теперь давайте реализуем тот же пример, используя оператор yield.

myList=[10,20,25,30,35,40,50] def mod(myList): for i in myList: if(i%10==0): yield i for i in mod(myList): print(i)

Получим следующий результат:

snimok4

Заключение

В этой статье мы провели сравнение yield и return в Python, перечислили все заметные различия между ними и разобрали это на примерах.

И return, и yield являются встроенными ключевыми словами (или операторами) Python. Оператор return используется для возврата значения из функции. При этом он прекращает выполнение программы. А оператор yield создает объект-генератор и может возвращать несколько значений, не прерывая выполнение программы.

Как работает yield

На StackOverflow часто задают вопросы, подробно освещённые в документации. Ценность их в том, что на некоторые из них кто-нибудь даёт ответ, обладающий гораздо большей степенью ясности и наглядности, чем может себе позволить документация. Этот — один из них.

Вот исходный вопрос:

Как используется ключевое слово yield в Python? Что оно делает?

Например, я пытаюсь понять этот код (**):

def _get_child_candidates(self, distance, min_dist, max_dist): if self._leftchild and distance - max_dist < self._median: yield self._leftchild if self._rightchild and distance + max_dist >= self._median: yield self._rightchild 

Вызывается он так:

result, candidates = list(), [self] while candidates: node = candidates.pop() distance = node._get_dist(obj) if distance = min_dist: result.extend(node._values) candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) return result 

Что происходит при вызове метода _get_child_candidates? Возвращается список, какой-то элемент? Вызывается ли он снова? Когда последующие вызовы прекращаются?

** Код принадлежит Jochen Schulz (jrschulz), который написал отличную Python-библиотеку для метрических пространств. Вот ссылка на исходники: http://well-adjusted.de/~jrschulz/mspace/

Итераторы

Для понимания, что делает yield, необходимо понимать, что такое генераторы. Генераторам же предшествуют итераторы. Когда вы создаёте список, вы можете считывать его элементы один за другим — это называется итерацией:

>>> mylist = [1, 2, 3] >>> for i in mylist : . print(i) 1 2 3 

Mylist является итерируемым объектом. Когда вы создаёте список, используя генераторное выражение, вы создаёте также итератор:

>>> mylist = [x*x for x in range(3)] >>> for i in mylist : . print(i) 0 1 4 

Всё, к чему можно применить конструкцию «for… in. », является итерируемым объектом: списки, строки, файлы… Это удобно, потому что можно считывать из них значения сколько потребуется — однако все значения хранятся в памяти, а это не всегда желательно, если у вас много значений.

Генераторы

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

>>> mygenerator = (x*x for x in range(3)) >>> for i in mygenerator : . print(i) 0 1 4 

Всё то же самое, разве что используются круглые скобки вместо квадратных. НО: нельзя применить конструкцию for i in mygenerator второй раз, так как генератор может быть использован только единожды: он вычисляет 0, потом забывает про него и вычисляет 1, завершаяя вычислением 4 — одно за другим.

Yield

Yield это ключевое слово, которое используется примерно как return — отличие в том, что функция вернёт генератор.

>>> def createGenerator() : . mylist = range(3) . for i in mylist : . yield i*i . >>> mygenerator = createGenerator() # создаём генератор >>> print(mygenerator) # mygenerator является объектом! >>> for i in mygenerator: . print(i) 0 1 4 

В данном случае пример бесполезный, но это удобно, если вы знаете, что функция вернёт большой набор значений, который надо будет прочитать только один раз.

Чтобы освоить yield, вы должны понимать, что когда вы вызываете функцию, код внутри тела функции не исполняется. Функция только возвращает объект-генератор — немного мудрёно 🙂

Ваш код будет вызываться каждый раз, когда for обращается к генератору.

Теперь трудная часть:

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

Генератор считается пустым, как только при исполнении кода функции не встречается yield. Это может случиться из-за конца цикла, или же если не выполняется какое-то из условий «if/else».

Объяснение кода из исходного вопроса

# Создаём метод узла, который будет возвращать генератор def _get_child_candidates(self, distance, min_dist, max_dist): # Этот код будет вызываться при каждом обращении к объекту-генератору: # Если у узла есть потомок слева # И с расстоянием всё в порядке, возвращаем этого потомка if self._leftchild and distance - max_dist < self._median: yield self._leftchild # Если у узла есть потомок справа # И с расстоянием всё в порядке, возвращаем этого потомка if self._rightchild and distance + max_dist >= self._median: yield self._rightchild # Если исполнение дошло до этого места, генератор считается пустым 
# Создаём пустой список и список со ссылкой на текущий объект result, candidates = list(), [self] # Входим в цикл по кандидатам (в начале там только один элемент) while candidates: # Вытягиваем последнего кандидата и удаляем его из списка node = candidates.pop() # Вычисляем расстояние между объектом и кандидатом distance = node._get_dist(obj) # Если с расстоянием всё в порядке, добавляем в результат if distance = min_dist: result.extend(node._values) # Добавляем потомков кандидата в список кандидатов, # чтобы цикл продолжал исполняться до тех пор, # пока не обойдёт всех потомков потомков кандидата candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) return result 
  • Цикл итерируется по списку, но списко расширяется во время итерации 🙂 Это лаконичный способ обойти все сгрупиррованные данные, зоть это и немного опасно, так как может обернуться бесконечным циклом. В таком случае candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) исчерпает все значения генератора, но при этом продолжит создавать новые объекты-генераторы, которые будут давать значения, отличные от предыдущих (поскольку применяются к к другим узлам).
  • Метод extend() это метод объекта списка, который ожидает на вход что-нибудь итерируемое и добавляет его значения к списку.
>>> a = [1, 2] >>> b = [3, 4] >>> a.extend(b) >>> print(a) [1, 2, 3, 4] 
  • Нет необходимости читать значения дважды.
  • Может случиться так, что потомков много и хранить их всех в памяти не хочется.

Читатель может остановиться здесь, или же прочитать ещё немного о продвинутом использовании генераторов:

Контроль за исчерпанием генератора

>>> class Bank(): # создаём банк, строящий торговые автоматы (ATM — Automatic Teller Machine) . crisis = False . def create_atm(self) : . while not self.crisis : . yield "$100" >>> hsbc = Bank() # когда всё хорошо, можно получить сколько угодно денег с торгового автомата >>> corner_street_atm = hsbc.create_atm() >>> print(corner_street_atm.next()) $100 >>> print(corner_street_atm.next()) $100 >>> print([corner_street_atm.next() for cash in range(5)]) ['$100', '$100', '$100', '$100', '$100'] >>> hsbc.crisis = True # пришёл кризис, денег больше нет! >>> print(corner_street_atm.next()) >>> wall_street_atm = hsbc.create_atm() # что верно даже для новых автоматов >>> print(wall_street_atm.next()) >>> hsbc.crisis = False # проблема в том, что когда кризис прошёл, автоматы по-прежнему пустые. >>> print(corner_street_atm.next()) >>> brand_new_atm = hsbc.create_atm() # но если построить ещё один, будешь снова в деле! >>> for cash in brand_new_atm : . print cash $100 $100 $100 $100 $100 $100 $100 $100 $100 . 

Это может оказаться полезным для разных целей вроде управления доступом к какому-нибудь ресурсу.

Ваш лучший друг Itertools

Модуль itertools содержит специальные функции для работы с итерируемыми объектами. Желаете продублировать генератор? Соединить два генератора последовательно? Сгруппировать значения вложенных списков в одну строчку? Применить map или zip без создания ещё одного списка?

Просто добавьте import itertools.

Хотите пример? Давайте посмотрим на возможные порядки финиширования на скачках (4 лошади):

>>> horses = [1, 2, 3, 4] >>> races = itertools.permutations(horses) >>> print(races) >>> print(list(itertools.permutations(horses))) [(1, 2, 3, 4), (1, 2, 4, 3), (1, 3, 2, 4), (1, 3, 4, 2), (1, 4, 2, 3), (1, 4, 3, 2), (2, 1, 3, 4), (2, 1, 4, 3), (2, 3, 1, 4), (2, 3, 4, 1), (2, 4, 1, 3), (2, 4, 3, 1), (3, 1, 2, 4), (3, 1, 4, 2), (3, 2, 1, 4), (3, 2, 4, 1), (3, 4, 1, 2), (3, 4, 2, 1), (4, 1, 2, 3), (4, 1, 3, 2), (4, 2, 1, 3), (4, 2, 3, 1), (4, 3, 1, 2), (4, 3, 2, 1)] 

Понимание внутреннего механизма итерации

Итерация это процесс, включающий итерируемые объекты (реализующие метод __iter__()) и итераторы (реализующие __next__()). Итерируемые объекты это любые объекты, из которых можно получить итератор. Итераторы это объекты, позволяющие итерировать по итерируемым объектам.

Больше информации по данному вопросу доступно в статье про то, как работает цикл for.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *