#python #generator #yield
#python #генератор #выход
Вопрос:
Я хочу написать функцию генератора Python, которая на самом деле никогда ничего не выдает. По сути, это выпадающий список «ничего не делать», который может использоваться другим кодом, который ожидает вызова генератора (но не всегда нуждается в результатах от него). Пока у меня есть это:
def empty_generator():
# ... do some stuff, but don't yield anything
if False:
yield
Теперь это работает нормально, но мне интересно, есть ли более выразительный способ сказать то же самое, то есть объявить функцию генератором, даже если она никогда не выдает никакого значения. Трюк, который я использовал выше, заключается в том, чтобы показать Python оператор yield внутри моей функции, даже если он недоступен.
Комментарии:
1. Из любопытства, почему это должен быть генератор? Я не могу представить, какой правильный код вызывающего абонента может явно требовать генератора…
2. @static_rtti Я только что обнаружил, что поддельные генераторы отлично работают для целей отложенной загрузки, в зависимости от контекста.
3. @Ekevoo не могли бы вы дать ссылку на пример?
4. @static_rtti здесь. Я уверен, что есть лучшие способы сделать то, что я сделал, но он выполнил свою работу. github.com/ekevoo/hfbr/blob /…
5. Для справки, я бы поставил
if False: yield
в верхней части функции, чтобы сразу было очевидно, что вы делаете. На самом деле в этом ее преимущество передreturn; yield
решением: в любом случае эта конструкция служит чем-то вроде аннотации, объявляющей «Я хочу, чтобы это был генератор», и мы обычно помещаем аннотации, которые влияют на всю функцию, вверху по уважительной причине — это помогает ориентировать читателя.
Ответ №1:
Другой способ
def empty_generator():
return
yield
На самом деле не «более выразительно», но короче. 🙂
Обратите внимание, что iter([])
or simply []
также подойдет.
Комментарии:
1. Мой голос отдается iter([]). [] на самом деле не является итератором (вы не можете вызвать .next() для него и т.д.)
2. @John: Обязательно добавьте комментарий, объясняющий, что он делает! 🙂
3. Это странно, потому что обычно возвращаемый результат не может быть вместе в коде определения функции. Но я заметил, что если return заменить на return 25 , возникает синтаксическая ошибка: ‘return’ с аргументом внутри генератора . Итак, есть разница между возвратом с аргументом и возвратом без аргумента. В чем эта разница на самом деле?
4. @eyquem:
return
в генераторе это то же самое, что поток, достигающий конца кода — это заставит генератор отбросить фрейм и поднять егоStopIteration
. Не имеет смысла возвращать значение, точно так же, как поток, достигающий конца кода, не возвращает значение.5. Обратите внимание, что теперь она действительна: PEP 380 .
Ответ №2:
Еще более короткое решение:
def empty_generator():
yield from []
Комментарии:
1. Интересно. Хотя для Python 3.
2. На мой взгляд, самый элегантный и удобный для проверки синтаксиса
3. Предложите использовать литерал кортежа
()
вместо литерала списка[]
, если вы хотите помочь Python избежать накладных расходов на создание пустого списка (в зависимости от того, насколько «умна» реализация Python, она, возможно, сможет распознать, что вы никогда не используете список, и оптимизировать соответствующим образом до такой степени, что это эквивалентно, но()
просто гарантировано неизменяемо языком, и это нормально и ожидается, что[] is not []
но() is ()
, и поэтому для того, чтобы реализация Python была()
эффективно бесплатной, требуется значительно меньше ума и усилий, чем для того, чтобы она была[]
почти бесплатной ).
Ответ №3:
Для максимальной читаемости и ремонтопригодности я бы отдал приоритет конструкции, которая находится в верхней части функции. Так что либо
- ваша исходная
if False: yield
конструкция, но перенесенная на самую первую строку, или - отдельный декоратор, который добавляет поведение генератора к вызываемому без генератора.
(Это при условии, что вам не просто нужен вызываемый объект, который что-то сделал, а затем вернул пустой iterable / iterator. Если это так, то вы могли бы просто использовать обычную функцию и return ()
/ return iter(())
в конце.)
Представьте, что читатель вашего кода видит:
def name_fitting_what_the_function_does():
# We need this function to be an empty generator:
if False: yield
# that crucial stuff that this function exists to do
Наличие этого в верхней части немедленно указывает каждому читателю этой функции на эту деталь, которая влияет на всю функцию — влияет на ожидания и интерпретации поведения и использования этой функции.
Какой длины тело вашей функции? Больше пары строк? Тогда, как читатель, я почувствую праведную ярость и осуждение по отношению к автору, если я не получу подсказки о том, что эта функция является генератором до самого конца, потому что я, вероятно, потратил значительные умственные затраты, создавая в своей голове модель, основанную на предположении, что это обычная функция — первая yield
в генераторе в идеале должна быть сразу видна, когда вы даже не знаете, как ее искать.
Кроме того, в функции длиной более нескольких строк более надежна конструкция в самом начале функции — я могу поверить, что любой, кто просматривал функцию, вероятно, видел ее первую строку каждый раз, когда они смотрели на нее. Это означает более высокую вероятность того, что если бы эта строка была ошибочной или прерывистой, кто-то бы это заметил. Это означает, что я могу быть менее бдительным в отношении возможности того, что все это на самом деле сломано, но используется таким образом, что делает поломку неочевидной.
Если вы работаете с людьми, которые достаточно бегло знакомы с работой Python, вы могли бы даже опустить этот комментарий, потому что для того, кто сразу помнит, что yield
это то, что заставляет Python превращать функцию в генератор, очевидно, что это эффект и, вероятно, намерение, поскольку нет другой причины, по которой правильный код не выполняется yield
.
В качестве альтернативы, вы могли бы пойти по пути декоратора:
@generator_that_yields_nothing
def name_fitting_what_the_function_does():
# that crucial stuff for which this exists
def generator_that_yields_nothing(wrapped):
@functools.wraps(wrapped)
def wrapper_generator():
if False: yield
wrapped()
return wrapper_generator
Комментарии:
1. Прошло десять лет, но я ценю этот вдумчивый ответ!