Однострочный, чтобы проверить, дает ли итератор хотя бы один элемент?

103

Сейчас делаю вот что:

try:
    something = iterator.next()
    # ...
except StopIteration:
    # ...

Но мне нужно выражение, которое я могу поместить в простое ifутверждение. Есть ли что-нибудь встроенное, что сделало бы этот код менее неуклюжим?

any()возвращается, Falseесли итерация пуста, но потенциально может перебирать все элементы, если это не так. Мне это нужно только для проверки первого пункта.


Кто-то спрашивает, что я пытаюсь сделать. Я написал функцию, которая выполняет SQL-запрос и возвращает его результаты. Иногда, когда я вызываю эту функцию, я просто хочу узнать, вернул ли запрос что-нибудь, и принять решение на основе этого.

Бастьен Леонар
источник
2
Также проблема с этим кодом заключается в том, что вы не можете упаковать его в функцию, потому что он съест первый элемент. Хороший вопрос.
andrewrk
2
В моем случае элемент мне вообще не нужен, я просто хочу знать, что есть хотя бы один элемент.
Bastien Léonard
2
ха! Мой тот же случай использования в попытке найти то же решение!
Daniel
Связанный: stackoverflow.com/q/661603/281545
Mr_and_Mrs_D
1
Также связано: hasNext в итераторах Python?
user2314737

Ответы:

135

anyне выйдет за пределы первого элемента, если он True. Если итератор выдает что-то ложное, вы можете написать any(True for _ in iterator).

Йохен Ритцель
источник
Кажется, это работает для меня с re.finditer. Вы можете легко проверить, что любая остановка с самого начала будет успешной: бегите, any((x > 100 for x in xrange(10000000)))а затем бегите any((x > 10000000 for x in xrange(100000000)))- второй должен занять гораздо больше времени.
chbrown
1
Это работает для случая «по крайней мере x»sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Дэйв Батлер
11
Точно так же, если вам нужно проверить, пуст ли итератор, можно использовать all(False for _ in iterator)будет проверять, пуст ли итератор. (all возвращает True, если итератор пуст, в противном случае он останавливается, когда видит первый элемент False)
KGardevoir
23
Большая проблема с этим решением заключается в том, что вы не можете использовать возвращаемое значение от итератора, если оно не пустое, верно?
Кен Уильямс
42

В Python 2.6+, если имя sentinelпривязано к значению, которое итератор не может выдать,

if next(iterator, sentinel) is sentinel:
    print('iterator was empty')

Если вы не знаете, что может дать итератор, сделайте свой собственный дозорный (например, в верхней части модуля) с помощью

sentinel = object()

В противном случае вы можете использовать в роли дозорного любое значение, которое вам «известно» (исходя из соображений приложения), которое итератор не может выдать.

Алекс Мартелли
источник
1
Ницца! Для моего случая использования этого if not next(iterator, None):было достаточно, поскольку я был уверен, что None не будет частью элементов. Спасибо, что указали мне правильное направление!
wasabigeek
1
@wasabi Помните, что notвернет True для любого ложного объекта, такого как пустые списки, False и ноль. is not Noneбезопаснее и на мой взгляд понятнее.
Caagr98,
21

Это не совсем чище, но показывает способ упаковать его в функцию без потерь:

def has_elements(iter):
  from itertools import tee
  iter, any_check = tee(iter)
  try:
    any_check.next()
    return True, iter
  except StopIteration:
    return False, iter

has_el, iter = has_elements(iter)
if has_el:
  # not empty

На самом деле это не питонизм, и для конкретных случаев, вероятно, есть лучшие (но менее общие) решения, такие как следующее по умолчанию.

first = next(iter, None)
if first:
  # Do something

Это не является общим, потому что None может быть допустимым элементом во многих итерациях.

Мэтью Флашен
источник
Это, наверное, лучший способ сделать это. Однако было бы полезно узнать, что пытается сделать OP? Вероятно, есть более элегантное решение (в конце концов, это IS Python).
rossipedia
Спасибо, думаю воспользуюсь next().
Bastien Léonard
1
@Bastien, хорошо, но сделайте это с подходящим дозорным (см. Мой ответ).
Alex Martelli
3
В этом решении огромная утечка памяти. teeВ itertools будет держать каждый элемент из исходного итератора в случае , если any_checkкогда - либо потребность в заранее. Это хуже, чем просто преобразование исходного итератора в список.
Рафал Довгирд,
1
@ RafałDowgird Это хуже, чем просто преобразование исходного итератора в список. Не совсем - подумайте о бесконечных последовательностях.
Петр Доброгост
6

ты можешь использовать:

if zip([None], iterator):
    # ...
else:
    # ...

но это немного необъяснимо для читателя кода

михал
источник
2
.. (вы можете использовать любую итерацию из 1 элемента вместо [None])
михал
5

Лучший способ сделать это - использовать peekablefrom more_itertools.

from more_itertools import peekable
iterator = peekable(iterator)
if iterator:
    # Iterator is non-empty.
else:
    # Iterator is empty.

Просто будьте осторожны, если вы сохранили ссылки на старый итератор, этот итератор будет расширяться. С этого момента вы должны использовать новый просматриваемый итератор. На самом деле, peekable ожидает быть единственным фрагментом кода, изменяющим этот старый итератор, так что вам все равно не следует хранить ссылки на старый итератор.

загадочныйФизик
источник
3

Что о:

In [1]: i=iter([])

In [2]: bool(next(i,False))
Out[2]: False

In [3]: i=iter([1])

In [4]: bool(next(i,False))
Out[4]: True
Нил
источник
4
Интересный! Но что, если next () вернет False, потому что это действительно результат?
Bastien Léonard
@ BastienLéonard Создайте класс class NotSet: pass, а затем проверьтеif next(i, NotSet) is NotSet: print("Iterator is empty")
Элайяс Дапшаускас
-1

__length_hint__ оценивает длину list(it)- это частный метод, однако:

x = iter( (1, 2, 3) )
help(x.__length_hint__)
      1 Help on built-in function __length_hint__:
      2 
      3 __length_hint__(...)
      4     Private method returning an estimate of len(list(it)).
мику
источник
4
не гарантируется для каждого итератора. >>> def it (): ... yield 1 ... yield 2 ... yield 3 ... >>> i = it () >>> i .__ length_hint__ Traceback (последний вызов последним): File " <stdin> ", строка 1, в <module> AttributeError: объект 'generator' не имеет атрибута ' length_hint '
andrewrk
3
Также, вероятно, законно, чтобы он возвращал 0 для итератора, который имеет более нуля записей, поскольку это просто подсказка.
Glenn Maynard
-1

Это избыточная оболочка итератора, которая обычно позволяет проверять, есть ли следующий элемент (путем преобразования в логическое значение). Конечно довольно неэффективно.

class LookaheadIterator ():

    def __init__(self, iterator):
        self.__iterator = iterator
        try:
            self.__next      = next (iterator)
            self.__have_next = True
        except StopIteration:
            self.__have_next = False

    def __iter__(self):
        return self

    def next (self):
        if self.__have_next:
            result = self.__next
            try:
                self.__next      = next (self.__iterator)
                self.__have_next = True
            except StopIteration:
                self.__have_next = False

            return result

        else:
            raise StopIteration

    def __nonzero__(self):
        return self.__have_next

x = LookaheadIterator (iter ([]))
print bool (x)
print list (x)

x = LookaheadIterator (iter ([1, 2, 3]))
print bool (x)
print list (x)

Вывод:

False
[]
True
[1, 2, 3]
удваивать
источник
-2

Немного поздно, но ... Вы можете превратить итератор в список, а затем работать с этим списком:

# Create a list of objects but runs out the iterator.
l = [_ for _ in iterator]

# If the list is not empty then the iterator had elements; else it was empty.
if l :
    pass # Use the elements of the list (i.e. from the iterator)
else :
    pass # Iterator was empty, thus list is empty.
Йенс
источник
4
Это неэффективно, поскольку перечисляет весь список. Не будет работать для бесконечных генераторов.
becko
@becko: Согласен. Но, похоже, это не так в исходном вопросе.
Jens
3
Другая проблема заключается в том, что итератор может генерировать бесконечное количество объектов, что может привести к переполнению памяти, и тот факт, что программа никогда не достигнет следующего оператора,
Виллем Ван Онсем,