Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
Официальная документация 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
- Как не быть тем парнем, а писать функции лучше
Комментарии