Использование for…else в генераторах Python
Вопрос
Я большой поклонник Python синтаксис for...else — удивительно, как часто это применимо и насколько эффективно может упростить код.
Однако я не нашел хорошего способа использовать его в генераторе, например:
def iterate(i):
for value in i:
yield value
else:
print 'i is empty'
В приведенном выше примере я бы хотел print
оператор будет выполнен только в том случае, если i
пусто.Однако, как else
только уважает break
и return
, он выполняется всегда, независимо от длины i
.
Если невозможно использовать for...else
таким образом, каков наилучший подход к этому, чтобы print
оператор выполняется только тогда, когда ничего не выдается?
Решение
Вы нарушаете определение генератора, который должен выдавать исключение StopIteration после завершения итерации (которое автоматически обрабатывается оператором возврата в функции генератора).
Так:
def iterate(i):
for value in i:
yield value
return
Лучше всего позволить вызывающему коду обрабатывать случай пустого итератора:
count = 0
for value in iterate(range([])):
print value
count += 1
else:
if count == 0:
print "list was empty"
Возможно, это более чистый способ сделать вышеописанное, но он должен работать нормально и не попадает ни в одну из распространенных ловушек «обращения с итератором как со списком», приведенных ниже.
Другие советы
Есть несколько способов сделать это.Вы всегда можете использовать Iterator
напрямую:
def iterate(i):
try:
i_iter = iter(i)
next = i_iter.next()
except StopIteration:
print 'i is empty'
return
while True:
yield next
next = i_iter.next()
Но если вы знаете больше о том, чего ожидать от спора i
, вы можете быть более кратким:
def iterate(i):
if i: # or if len(i) == 0
for next in i:
yield next
else:
print 'i is empty'
raise StopIteration()
Подводя итог некоторым предыдущим ответам, это можно решить следующим образом:
def iterate(i):
empty = True
for value in i:
yield value
empty = False
if empty:
print "empty"
так что на самом деле здесь нет пункта «else».
Как вы заметили, for..else
обнаруживает только break
.Так что это применимо только тогда, когда вы что-то ищете, а затем останавливаться.
Для вашей цели он неприменим не потому, что это генератор, а потому что вы хотите обработать все элементы, не останавливаясь (потому что вы хотите отдать их все, но дело не в этом).
Генератор или нет, вам действительно нужно логическое значение, как в решении Бера.
Если невозможно использовать for...else таким образом, каков наилучший подход к этому, чтобы оператор печати выполнялся только тогда, когда ничего не выдается?
Максимум, что я могу придумать:
>>> empty = True
>>> for i in [1,2]:
... empty = False
... if empty:
... print 'empty'
...
>>>
>>>
>>> empty = True
>>> for i in []:
... empty = False
... if empty:
... print 'empty'
...
empty
>>>
А как насчет простого if-else?
def iterate(i):
if len(i) == 0: print 'i is empty'
else:
for value in i:
yield value