Ключевое слово 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 += 11 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
Ключевое слово yield используется для остановки и возобновления функций-генераторов ( function* или legacy generator function (en-US) ).
Интерактивный пример
Синтаксис
[rv] = yield [[выражение]];Возвращаемое выражение. Если не указано, то возвращается значение undefined .
Возвращает необязательное значение, которое передаётся в next() генератора, чтобы возобновить его выполнение.
Описание
Ключевое слово yield вызывает остановку функции-генератора и возвращает текущее значение выражения, указанного после ключевого слова yield . Его можно рассматривать как аналог ключевого слова return в функции-генераторе.
На самом деле ключевое слово yield возвращает объект с двумя параметрами, value и done . При этом, value является результатом вычисления выражения после yield , а done указывает, была ли завершена функция-генератор.
Во время остановки на операторе yield , выполнение кода в функции-генераторе не возобновится, пока не будет вызван метод next() возвращаемого функцией объекта-генератора. Это предоставляет непосредственный контроль за выполнением генератора и возвратом его значений.
Примеры
Следующий фрагмент кода содержит определение функции-генератора и вспомогательной функции:
function* foo() var index = 0; while (index 2) // при достижении 2, done в yield станет true, а value undefined; yield index++; >После того как тело функции-генератора определено, оно может использоваться для получения итератора:
var iterator = foo(); console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); // console.log(iterator.next()); //Спецификации
| Specification |
|---|
| ECMAScript Language Specification # prod-YieldExpression |
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 7 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
MDN
Support
- Product help
- Report an issue
Our communities
Developers
- Web Technologies
- Learn Web Development
- MDN Plus
- Hacks Blog
- Website Privacy Notice
- Cookies
- Legal
- Community Participation Guidelines
Visit Mozilla Corporation’s not-for-profit parent, the Mozilla Foundation.
Portions of this content are ©1998– 2023 by individual mozilla.org contributors. Content available under a Creative Commons license.
Выражение yield from в Python
Для простых итераторов iterable выражение yield from iterable - это просто сокращенная форма for item in iterable: yield item :
def gen_list1(iterable): for i in list(iterable): yield i # эквивалентно def gen_list2(iterable): yield from list(iterable) >>> list(gen_list1('python')) # ['p', 'y', 't', 'h', 'o', 'n'] >>> list(gen_list2('home')) # ['h', 'o', 'm', 'e']
Что если iterable , то же будет генератором?
В отличие от обычного цикла, yield from позволяет другому генератору получать отправленные и брошенные значения непосредственно из вызывающей области и возвращать окончательное значение внешнему генератору.
Хотя выражение yield from предназначено главным образом для передачи части вычислений под-генератору, оно фактически позволяет передавать управление произвольным генераторам.
Как это работает:
Когда выполнение натыкается на выражение yield from , все приходящие через generator.send() /generator. __next__() запросы проксируются в , а значения yield соответственно передаются сразу наружу. Так происходит до тех пор, пока не кончится, т. е. не выбросит исключение StopIteration , после этого выполнение внешнего генератора продолжается.
Все это делает генераторы очень похожими на сопрограммы. Они вызываются несколько раз, у них более одной точки входа, и их выполнение может быть приостановлено. Единственное отличие состоит в том, что функции генератора не может контролировать, где выполнение должно продолжаться после ее завершения и управление всегда передается вызывающей стороне генератора.
Примеры использования:
def accumulate(): tally = 0 while 1: next = yield if next is None: return tally tally += next def gather_tallies(tallies): while 1: tally = yield from accumulate() tallies.append(tally) >>> tallies = [] >>> acc = gather_tallies(tallies) # Смотрим, что `accumulate()` готов принять значения >>> next(acc) >>> for i in range(4): . acc.send(i) . # Завершить первый подсчет >>> acc.send(None) >>> for i in range(5): . acc.send(i) . # Завершите второй подсчет >>> acc.send(None) >>> tallies # [6, 10]
Основная принцип введения выражения yield from заключается в том, чтобы даже генераторы, разработанные для использования с методами generator.send() и generator.throw() , можно было разделить на несколько под-генераторов так же легко, как одну большую функцию можно разделить на несколько под-функций.
- КРАТКИЙ ОБЗОР МАТЕРИАЛА.
- Преобразование простой функции в генератор Python
- Передача значений в генератор Python
- Использование send(), throw() и close() в генераторах Python
- Выражение yield
- Выражение yield from
- Выражение-генератор
- Обработка больших данных при помощи генераторов Python
Как работает yield
Итератор по сути представляет блок кода, который использует оператор yield для перебора набора значений. Данный блок кода может представлять тело метода, оператора или блок get в свойствах.
Итератор использует две специальных инструкции:
- yield return : определяет возвращаемый элемент
- yield break : указывает, что последовательность больше не имеет элементов
Рассмотрим небольшой пример:
Numbers numbers = new Numbers(); foreach (int n in numbers) < Console.WriteLine(n); >class Numbers < public IEnumeratorGetEnumerator() < for (int i = 0; i < 6; i++) < yield return i * i; >> >
В классе Numbers метод GetEnumerator() фактически представляет итератор. С помощью оператора yield return возвращается некоторое значение (в данном случае квадрат числа).
В программе с помощью цикла foreach мы можем перебрать объект Numbers как обычную коллекцию. При получении каждого элемента в цикле foreach будет срабатывать оператор yield return, который будет возвращать один элемент и запоминать текущую позицию.
Благодаря итераторам мы можем пойти дальше и легко реализовать перебор числа в цикле foreach:
foreach(var n in 5) Console.WriteLine(n); foreach (var n in -5) Console.WriteLine(n); static class Int32Extension < public static IEnumeratorGetEnumerator(this int number) < int k = (number >0)? number: 0; for (int i = number - k; i >
В данном случае итератор реализован как метод расширения для типа int или System.Int32. В методе итератора фактически возвращаем все целочисленные значения от 0 до текущего числа. Консольный вывод:
0 1 2 3 4 5 -5 -4 -3 -2 -1 0
Другой пример: пусть у нас есть коллекция Company, которая представляет компанию и которая хранит в массиве personnel штат сотрудников - объектов Person. Используем оператор yield для перебора этой коллекции:
class Person < public string Name < get; >public Person(string name) =>Name = name; > class Company < Person[] personnel; public Company(Person[] personnel) =>this.personnel = personnel; public int Length => personnel.Length; public IEnumerator GetEnumerator() < for (int i = 0; i < personnel.Length; i++) < yield return personnel[i]; >> >
Метод GetEnumerator() представляет итератор. И когда мы будем осуществлять перебор в объекте Company в цикле foreach, то будет идти обращение к вызову yield return personnel[i]; . При обращении к оператору yield return будет сохраняться текущее местоположение. И когда метод foreach перейдет к следующей итерации для получения нового объекта, итератор начнет выполнения с этого местоположения.
Ну и в основной программе в цикле foreach выполняется собственно перебор, благодаря реализации итератора:
var people = new Person[] < new Person("Tom"), new Person("Bob"), new Person("Sam") >; var microsoft = new Company(people); foreach(Person employee in microsoft)
Хотя при реализации итератора в методе GetEnumerator() применялся перебор массива в цикле for, но это необязательно делать. Мы можем просто определить несколько вызовов оператора yield return :
public IEnumerator GetEnumerator()
В этом случае при каждом вызове оператора yield return итератор также будет запоминать текущее местоположение и при последующих вызовах начинать с него.
Именованный итератор
Выше для создания итератора мы использовали метод GetEnumerator . Но оператор yield можно использовать внутри любого метода, только такой метод должен возвращать объект интерфейса IEnumerable . Подобные методы еще называют именованными итераторами .
Создадим такой именованный итератор в классе Company и используем его:
class Person < public string Name < get; >public Person(string name) =>Name = name; > class Company < Person[] personnel; public Company(Person[] personnel) =>this.personnel = personnel; public int Length => personnel.Length; public IEnumerable GetPersonnel(int max) < for (int i = 0; i < max; i++) < if (i == personnel.Length) < yield break; >else < yield return personnel[i]; >> > >
Определенный здесь итератор - метод IEnumerable GetPersonnel(int max) в качестве параметра принимает количество выводимых объектов. В процессе работы программы может сложиться, что его значение будет больше, чем длина массива personnel. И чтобы не произошло ошибки, используется оператор yield break . Этот оператор прерывает выполнение итератора.
var people = new Person[] < new Person("Tom"), new Person("Bob"), new Person("Sam") >; var microsoft = new Company(people); foreach(Person employee in microsoft.GetPersonnel(5))
Вызов microsoft.GetPersonnel(5) будет возвращать набор из не более чем 5 объектов Person. Но так как у нас всего три таких объекта, то в методе GetPersonnel после трех операций сработает оператор yield break .