Идиоматический способ принять меры по попытке петли над пустым утеряемым
-
28-09-2019 - |
Вопрос
Предположим, что я зацикливаюсь по поводу итеративным и хотел бы предпринять некоторые действия, если итератор пуст. Два лучших способа, о которых я могу думать, что это делается:
for i in iterable:
# do_something
if not iterable:
# do_something_else
и
empty = True
for i in iterable:
empty = False
# do_something
if empty:
# do_something_else
Первый зависит от того, что имеется в том, что является коллекцией (так бесполезно, когда то, когда ее передач передается в функцию / метод, в котором есть цикл) и второй наборы empty
на каждом проходе через петлю, которая кажется уродливым.
Есть ли другой способ, которым я скучаю или вторую альтернативу лучшим? Было бы действительно круто, если бы был какой-то пункт, который я мог бы добавить в оператор петли, которое будет обрабатывать это для меня, как else
произносить not_found
Флаги уходят.
Я не ищу умные хаки.
Я не ищу решений, которые включают много кода
Я ищу простому языку. Я ищу ясно и питон Способ итерации по поводу итерации и возьмите некоторое действие, если у счет будет пуст, чтобы любой опытный программист Python будет понимать. Если бы я мог сделать это, не установив флаг на каждой итерации, это было бы фантастическим. Если нет простой идиомы, которая делает это, то забудь об этом.
Решение
Я думаю, что это самый чистый способ сделать это:
# first try with exceptions
def nonempty( iter ):
""" returns `iter` if iter is not empty, else raises TypeError """
try:
first = next(iter)
except StopIteration:
raise TypeError("Emtpy Iterator")
yield first
for item in iter:
yield item
# a version without exceptions. Seems nicer:
def isempty( iter ):
""" returns `(True, ())` if `iter` if is empty else `(False, iter)`
Don't use the original iterator! """
try:
first = next(iter)
except StopIteration:
return True, ()
else:
def iterator():
yield first
for item in iter:
yield item
return False, iterator()
for x in ([],[1]):
# first version
try:
list(nonempty(iter(x))) # trying to consume a empty iterator raises
except TypeError:
print x, "is empty"
else:
print x, "is not empty"
# with isempty
empty, it = isempty(iter(x))
print x, "is", ("empty" if empty else "not empty")
Другие советы
Это довольно взлома, но вы можете удалить i
А затем проверьте, существует ли он после цикла (если нет, цикл никогда не случился):
try:
del i
except NameException: pass
for i in iterable:
do_something(i)
try:
del i
except NameException:
do_something_else()
Я думаю, что это, вероятно, уродье, чем просто использовать флаг, хотя
Обновление 2.
мне понравилось Ответ Одомонтоно. Отказ ИМХО лучше подходит для этой проблемы, чем то, что я написал ниже.
Обновлять
(После прочтения комментариев ОП и отредактированный вопрос) вы можете сделать это тоже. Смотри ниже:
def with_divisible(n, a, b, f):
it = (i for i in xrange(a, b) if not i % n)
for i in wrapper(it):
f(i)
>>> with_divisible(1, 1, 1, lambda x: x)
Traceback (most recent call last):
File "<pyshell#55>", line 1, in <module>
with_divisible(1, 1, 1, lambda x: x)
File "<pyshell#54>", line 3, in with_divisible
for i in wrapper(it):
File "<pyshell#46>", line 4, in wrapper
raise EmptyIterableException("Empty")
EmptyIterableException: Empty
>>> with_divisible(7, 1, 21, lambda x: x)
7
14
...Snipped...
raise EmptyIterableException("Empty")
EmptyIterableException: Empty
Оригинальный ответ
Интересная проблема. Я сделал несколько экспериментов и придумал следующее:
class EmptyIterableException(Exception):
pass
def wrapper(iterable):
for each in iterable:
yield each
raise EmptyIterableException("Empty")
try:
for each in wrapper(iterable):
do_something(each)
except EmptyIterableException, e:
do_something_else()
if not map(do_something_callable,iterable) :
# do something else
Общий путь вперед, если итератор должен быть частично проверен, прежде чем потребляться, - это использовать itertools.tee
. Отказ Таким образом, мы можем иметь два копии итератора и проверять одну для пустоты, все еще потребляя другую копию с самого начала.
from itertools import tee
it1, it2 = tee(iterable)
try:
it1.next()
for i in it2:
do_some_action(i) #iterator is not empty
except StopIteration:
do_empty_action() #iterator is empty
То StopIteration
Исключение обязательно должно быть результатом вызова it1.next()
, как любое StopIteration
Исключения подняли меду внутри цикла, будут прекращать эту петлю.
Редактировать: Для тех, кто не любит такие исключения, islice
Может использоваться для настройки одного шагового цикла:
from itertools import tee, islice
it1, it2 = tee(iterable)
for _ in islice(it1, 1):
#loop entered if iterator is not empty
for i in it2:
do_some_action(i)
break #if loop entered don't execute the else section
else:
do_empty_action()
Я лично предпочитаю первый стиль. YMMV.
Как насчет обратного изменения "если" и "для":
if iterable:
for i in iterable:
do_something(i)
else:
do_something_else()
Хорошо, это не работает!
Вот другое решение: http://code.activeState.com/Recipes/413614-testing-for-an-empte-iter-itorter/
Это сочетание Майкл Мрозек'песок FM.Ответы:
def with_divisible(n, a, b, f):
'''apply f to every integer x such that n divides x and a <= x < b'''
it = (i for i in xrange(a, b) if not i % n)
for i in it:
f(i)
try: i # test if `it` was empty
except NameError: print('do something else')
def g(i):
print i,
with_divisible( 3, 1, 10, g) # Prints 3 6 9.
with_divisible(33, 1, 10, g) # Prints "do something else"
Генераторы имеют свойство «Gi_Frame», который отсутствует, когда генератор исчерпан, но только после закрепления был поднят. Если это приемлемо, вот то, что вы могли бы попробовать:
import types
def do(x, f, f_empty):
if type(x) == types.GeneratorType:
# generators have a 'gi_frame' property,
# which is None once the generator is exhausted
if x.gi_frame:
# not empty
return f(x)
return f_empty(x)
if x:
return f(x)
return f_empty(x)
def nempty(lst):
print lst, 'not empty'
def empty(lst):
print 'Twas empty!'
# lists
do([2,3,4], nempty, empty)
do([], nempty, empty)
# generators
do((i for i in range(5)), nempty, empty)
gen = (i for i in range(1))
gen.next()
try:
gen.next()
except StopIteration:
pass
do(gen, nempty, empty)