Одне вкладиш, щоб перевірити, чи дає ітератор щонайменше один елемент?


101

На даний момент я роблю це:

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

Але я хотів би вираз, який я міг би розмістити в простому ifвисловлюванні. Чи є щось вбудоване, що могло б зробити цей код менш незграбним?

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


Хтось запитує, що я намагаюся зробити. Я написав функцію, яка виконує запит SQL і дає результати. Іноді, коли я викликаю цю функцію, я просто хочу знати, чи запит щось повернув, і прийняти рішення на основі цього.


2
Також проблема з цим кодом полягає в тому, що ви не можете упакувати його у функцію, оскільки він з’їсть перший елемент. Гарне питання.
andrewrk

2
У моєму випадку мені елемент взагалі не потрібен, я просто хочу знати, що є принаймні один елемент.
Бастієн Леонар

2
ха-ха! Мій той самий варіант використання, намагаючись знайти те саме рішення!
Даніель


1
Також пов'язано: hasNext в ітераторах Python?
user2314737

Відповіді:


134

anyне буде виходити за межі першого елемента, якщо це True. Якщо ітератор видасть щось помилково, ви можете написати any(True for _ in iterator).


Мені здається, це працює для мене, з повторним пошуком. Ви можете перевірити, що будь-які зупинки спочатку легко виконуються: бігайте, any((x > 100 for x in xrange(10000000)))а потім бігайте any((x > 10000000 for x in xrange(100000000)))- друга повинна зайняти набагато більше часу.
chbrown

1
Це працює для випадку "принаймні х"sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Дейв Батлер

11
Подібним чином, якщо вам потрібно перевірити, чи ітератор порожній, можна використовувати all(False for _ in iterator)перевірку, чи ітератор порожній. (все повертає True, якщо ітератор порожній, інакше він зупиняється, коли бачить перший False-елемент)
KGardevoir

22
Основна проблема цього рішення полягає в тому, що ви насправді не можете використовувати повернене значення з ітератора, якщо воно не порожнє, правда?
Кен Вільямс

42

У Python 2.6+, якщо ім'я sentinelпов'язане зі значенням, яке ітератор не може отримати,

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

Якщо ви не уявляєте, що може дати ітератор, створіть свій власний вартовий (наприклад, у верхній частині вашого модуля) з

sentinel = object()

В іншому випадку, ви можете використовувати в ролі сторожа будь-яке значення, яке ви "знаєте" (на основі міркувань програми), яке ітератор не може дати.


1
Приємно! Для мого випадку використання if not next(iterator, 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 може бути допустимим елементом у багатьох ітерабелях.


Це, мабуть, найкращий спосіб зробити це. Однак допомогло б знати, що намагається зробити ОП? Ймовірно, є більш елегантне рішення (зрештою, це IS Python).
rossipedia

Дякую, я думаю, що буду використовувати next().
Bastien Léonard

1
@Bastien, чудово, але зробіть це за допомогою відповідного сторожа (див. Мою відповідь).
Alex Martelli

3
У цьому рішенні величезний витік пам'яті. Інструментам teeinertools доведеться зберігати кожен окремий елемент з оригінального ітератора на випадок, якщо any_checkпотрібно буде просунутися вперед. Це гірше, ніж просто перетворення оригінального ітератора в список.
Рафал Довгірд

1
@ RafałDowgird Це гірше, ніж просто перетворення оригінального ітератора в список. Не зовсім - подумайте про нескінченні послідовності.
Пьотр Доброгост

6

Ви можете використовувати:

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

але це трохи незрозуміло для читача коду


2
.. (ви можете використовувати будь-який 1-позиційний ітерабель замість [None])
mykhal

5

Найкращий спосіб зробити це за допомогою a 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
Цікавий! Але що робити, якщо наступний () повертає Хибне, тому що це те, що було справді отримано?
Бастієн Леонард

@ BastienLéonard Створити клас class NotSet: pass, а потім перевіритиif next(i, NotSet) is NotSet: print("Iterator is empty")
Elijas

-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 (останній виклик останній): Файл " <stdin> ", рядок 1, в <module> AttributeError: об'єкт 'generator' не має атрибуту ' length_hint '
andrewrk

3
Можливо, також законно повертати 0 для ітератора, який має більше ніж нуль записів, оскільки це лише підказка.
Гленн Мейнард

-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: Погоджено. Але, схоже, це не так у вихідному питанні.
Єнс

3
Інша проблема полягає в тому, що ітератор може генерувати нескінченну кількість об'єктів, що може призвести до переповнення пам'яті, і той факт, що програма ніколи не дійде до наступного висловлювання
Віллем Ван Онсем
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.