Самый популярный Python-вопрос на Stackoverflow связан с ключевым словом yield. Разберемся с его назначением и особенностями использования. Официальная документация Python содержит достаточно подробное описание всех функции языка и немало примеров. Тем не менее назначение некоторых ключевых слов ставит начинающих разработчиков в тупик. Прежде всего это касается 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 <= max_dist and distance >= min_dist: result.extend(node._values) candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) return result Что происходит в момент вызова метода _get_child_candidates? Что он возвращает – список или элемент? Вызывается ли метод повторно, и когда прекращаются последующие вызовы? А вот и лучший ответ на вопрос о yield в Python: Для понимания того, что делает 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, являются итерируемыми – списки, строки, файлы. Итерируемые объекты очень удобны, потому что они не ограничивают количество повторных считываний данных. Однако вся информация находится в оперативной памяти, и при большом объеме данных это нежелательно. Генераторы Генераторы также относятся к итерируемым объектам, однако данные из них можно считать только один раз. Генераторы не хранят значения в памяти, а создают их на лету: >>> mygenerator = (x*x for x in range(3)) >>> for i in mygenerator: ... print(i) 0 1 4 Генераторы используются так же, как и списковые включения, отличие заключается в применении круглых скобок () вместо квадратных []. Кроме того, в отличие от списка, сгенерированного с помощью спискового включения, к генератору нельзя обратиться повторно – вычисляя каждый последующий элемент, генератор «забывает» о предыдущем. yield Ключевое слово yield используется в функциях так же, как и return – для возвращения результата работы. Разница заключается в том, что yield возвращает генератор. >>> def create_generator(): ... mylist = range(3) ... for i in mylist: ... yield i*i ... >>> mygenerator = create_generator() # create a generator >>> print(mygenerator) # mygenerator is an object! <generator object create_generator at 0xb7555c34> >>> for i in mygenerator: ... print(i) 0 1 4 Использовать yield вместо return стоит в тех случаях, когда функция возвращает большой объем данных, которые достаточно прочитать один раз. Для эффективного использования нужно понимать главную особенность yield: при вызове функции код в теле функции не исполняется. Функция просто возвращает объект-генератор. Код вызывается каждый раз, когда for обращается к генератору. При первом запуске функции она будет исполняться, пока не дойдет до yield, после чего вернет первое значение из цикла. При каждом последующем вызове будет происходить следующая итерация и возвращение значения цикла. Процесс будет повторяться, пока генератор не окажется пустым. Генератор считается пустым, если функция не встречает yield – это происходит либо в конце цикла, либо при невыполнении условий if и else. Пояснение кода, приведенного в вопросе Генератор: # Создание метода узла, который будет возвращать генератор def _get_child_candidates(self, distance, min_dist, max_dist): # Код будет вызываться при каждом обращении к объекту-генератору # Если слева от узла есть потомок # И расстояние соответствует условию, yield вернет этого потомка if self._leftchild and distance - max_dist < self._median: yield self._leftchild # Если есть потомок справа от узла # И расстояние соответствует условию, yield вернет этого потомка 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 <= max_dist and 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)) завершит использование всех значений генератора, но цикл while не остановится на этом, а продолжит процесс создания новых объектов-генераторов, поставляющих значения, отличающиеся от предыдущих (потому что они относятся к другим узлам). В коде используется метод extend(), который принимает итерируемые объекты и добавляет их к списку. Метод extend() обычно используется для добавления в список другого списка: >>> a = [1, 2] >>> b = [3, 4] >>> a.extend(b) >>> print(a) [1, 2, 3, 4] Однако в рассматриваемом коде extend() принимает не список, а генератор, что значительно оптимизирует программу: отпадает необходимость повторного считывания данных; не нужно хранить в памяти множество потомков. Метод extend() может добавлять в список любые итерируемые объекты – генераторы, строки, кортежи, списки. Это называется утиной типизацией. Где еще пригодится yield Использование yield решает проблему перегрузки памяти при работе с объемными файлами. К примеру, считывание объемных csv-файлов часто приводит к зависанию и прерыванию программы с ошибкой MemoryError. Чтобы не загружать в память весь файл сразу, и считывать только нужные строки, применяется yield. Этот код, к примеру, вернет количество строк в файле: def csv_reader(file_name): for row in open(file_name, "r"): yield row С помощью yield можно сгенерировать бесконечную (в отличие от range) последовательность чисел: def infinite_sequence(): num = 0 while True: yield num num += 1 Выполнение этого кода будет продолжаться до ручного прерывания. Заключение В отличие от return, который отправляет вызывающей стороне определенное значение, yield может создавать последовательность значений. Использование yield целесообразно в тех случаях, когда нужно выполнить итерацию по последовательности значений, но при этом хранить всю последовательность в памяти нежелательно. Yield используются в генераторах Python. Функция-генератор определяется как обычная функция, но всякий раз, когда ей нужно выдать значение, она делает это с помощью ключевого слова yield, а не return. Если тело def содержит yield, функция автоматически становится генераторной. *** Материалы по теме Как в Python применяются вложенные функции Упрощаем разработку: асинхронные функции Python Как не быть тем парнем, а писать функции лучше
Официальная документация Python содержит достаточно подробное описание всех функции языка и немало примеров. Тем не менее назначение некоторых ключевых слов ставит начинающих разработчиков в тупик. Прежде всего это касается yield – не случайно вопрос о нем остается самым популярным на Stackoverflow.
yield
Вопрос звучит так:
Как используется ключевое слово 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 <= max_dist and distance >= min_dist: result.extend(node._values) candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) return result
Что происходит в момент вызова метода _get_child_candidates? Что он возвращает – список или элемент? Вызывается ли метод повторно, и когда прекращаются последующие вызовы?
_get_child_candidates
А вот и лучший ответ на вопрос о yield в Python:
Для понимания того, что делает 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, являются итерируемыми – списки, строки, файлы. Итерируемые объекты очень удобны, потому что они не ограничивают количество повторных считываний данных. Однако вся информация находится в оперативной памяти, и при большом объеме данных это нежелательно.
for
Генераторы также относятся к итерируемым объектам, однако данные из них можно считать только один раз. Генераторы не хранят значения в памяти, а создают их на лету:
>>> mygenerator = (x*x for x in range(3)) >>> for i in mygenerator: ... print(i) 0 1 4
Генераторы используются так же, как и списковые включения, отличие заключается в применении круглых скобок () вместо квадратных []. Кроме того, в отличие от списка, сгенерированного с помощью спискового включения, к генератору нельзя обратиться повторно – вычисляя каждый последующий элемент, генератор «забывает» о предыдущем.
()
[]
Ключевое слово yield используется в функциях так же, как и return – для возвращения результата работы. Разница заключается в том, что yield возвращает генератор.
return
>>> def create_generator(): ... mylist = range(3) ... for i in mylist: ... yield i*i ... >>> mygenerator = create_generator() # create a generator >>> print(mygenerator) # mygenerator is an object! <generator object create_generator at 0xb7555c34> >>> for i in mygenerator: ... print(i) 0 1 4
Использовать yield вместо return стоит в тех случаях, когда функция возвращает большой объем данных, которые достаточно прочитать один раз.
Для эффективного использования нужно понимать главную особенность yield: при вызове функции код в теле функции не исполняется. Функция просто возвращает объект-генератор. Код вызывается каждый раз, когда for обращается к генератору. При первом запуске функции она будет исполняться, пока не дойдет до yield, после чего вернет первое значение из цикла. При каждом последующем вызове будет происходить следующая итерация и возвращение значения цикла. Процесс будет повторяться, пока генератор не окажется пустым. Генератор считается пустым, если функция не встречает yield – это происходит либо в конце цикла, либо при невыполнении условий if и else.
if
else
Генератор:
# Создание метода узла, который будет возвращать генератор def _get_child_candidates(self, distance, min_dist, max_dist): # Код будет вызываться при каждом обращении к объекту-генератору # Если слева от узла есть потомок # И расстояние соответствует условию, yield вернет этого потомка if self._leftchild and distance - max_dist < self._median: yield self._leftchild # Если есть потомок справа от узла # И расстояние соответствует условию, yield вернет этого потомка 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 <= max_dist and 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)) завершит использование всех значений генератора, но цикл while не остановится на этом, а продолжит процесс создания новых объектов-генераторов, поставляющих значения, отличающиеся от предыдущих (потому что они относятся к другим узлам).
while
В коде используется метод extend(), который принимает итерируемые объекты и добавляет их к списку. Метод extend() обычно используется для добавления в список другого списка:
extend()
>>> a = [1, 2] >>> b = [3, 4] >>> a.extend(b) >>> print(a) [1, 2, 3, 4]
Однако в рассматриваемом коде extend() принимает не список, а генератор, что значительно оптимизирует программу:
Метод extend() может добавлять в список любые итерируемые объекты – генераторы, строки, кортежи, списки. Это называется утиной типизацией.
Использование yield решает проблему перегрузки памяти при работе с объемными файлами. К примеру, считывание объемных csv-файлов часто приводит к зависанию и прерыванию программы с ошибкой MemoryError. Чтобы не загружать в память весь файл сразу, и считывать только нужные строки, применяется yield. Этот код, к примеру, вернет количество строк в файле:
MemoryError
def csv_reader(file_name): for row in open(file_name, "r"): yield row
С помощью yield можно сгенерировать бесконечную (в отличие от range) последовательность чисел:
range
def infinite_sequence(): num = 0 while True: yield num num += 1
Выполнение этого кода будет продолжаться до ручного прерывания.
В отличие от return, который отправляет вызывающей стороне определенное значение, yield может создавать последовательность значений. Использование yield целесообразно в тех случаях, когда нужно выполнить итерацию по последовательности значений, но при этом хранить всю последовательность в памяти нежелательно.
Yield используются в генераторах Python. Функция-генератор определяется как обычная функция, но всякий раз, когда ей нужно выдать значение, она делает это с помощью ключевого слова yield, а не return. Если тело def содержит yield, функция автоматически становится генераторной.
Yield
def
***
Ваш адрес email не будет опубликован. Обязательные поля помечены *
Сохранить моё имя, email и адрес сайта в этом браузере для последующих моих комментариев.
Δ
Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.