Идиоматический способ предпринять действия при попытке перебрать пустой итерируемый - PullRequest
5 голосов
/ 15 августа 2010

Предположим, что я перебираю итерацию и хотел бы предпринять некоторые действия, если итератор пуст.Вот два лучших способа сделать это:

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 исчезнуть.


Я не ищу умных хаков.

Я не ищу решений, которые содержат много кода

Я ищу простую языковую функцию.Я ищу clear и pythonic способ перебора итерируемого и выполнения некоторых действий, если итерируемое пусто, что будет понятно любому опытному программисту на python.Если бы я мог сделать это без установки флага на каждой итерации, это было бы фантастически.Если нет простой идиомы, которая делает это, тогда забудьте об этом.

Ответы [ 8 ]

3 голосов
/ 15 августа 2010

Я думаю, что это самый чистый способ сделать это:

# 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")
3 голосов
/ 15 августа 2010

Это довольно хакерски, но вы можете удалить i и затем проверить, существует ли он после цикла (если нет, цикл никогда не происходил):

try:
    del i
except NameException: pass

for i in iterable:
    do_something(i)

try:
    del i
except NameException:
    do_something_else()

Я думаю, что это, вероятно, более уродливо, чем простоиспользуя флаг хотя

2 голосов
/ 15 августа 2010
if not map(do_something_callable,iterable) : 
    # do something else
2 голосов
/ 15 августа 2010

Обновление 2

Мне понравился Odomontois 'ответ . ИМХО, он лучше подходит для этой проблемы, чем то, что я написал ниже.

Обновление

(После прочтения комментария ОП и отредактированного вопроса) Вы тоже можете это сделать. Смотрите ниже:

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()
1 голос
/ 15 августа 2010

Общий путь вперед, если итератор должен быть частично проверен перед использованием, состоит в использовании 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.

0 голосов
/ 15 августа 2010

Генераторы имеют свойство 'gi_frame', которое равно None после того, как генератор исчерпан, но только после того, как StopIteration было поднято. Если это приемлемо, вот что вы можете попробовать:

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)
0 голосов
/ 15 августа 2010

Это комбинация ответов Михаила Мрозека и 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"
0 голосов
/ 15 августа 2010

А как насчет изменения "если" и "для":

if iterable:
    for i in iterable:
        do_something(i)
else:
    do_something_else()

ОК, это не работает!

Вот другое решение: http://code.activestate.com/recipes/413614-testing-for-an-empty-iterator/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...