One-liner untuk memeriksa apakah iterator menghasilkan setidaknya satu elemen?


101

Saat ini saya melakukan ini:

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

Tapi saya ingin ekspresi yang bisa saya tempatkan di dalam ifpernyataan sederhana . Apakah ada fitur bawaan yang dapat membuat kode ini terlihat tidak terlalu kikuk?

any()mengembalikan Falsejika sebuah iterable kosong, tetapi itu akan berpotensi mengulang semua item jika tidak. Saya hanya perlu untuk memeriksa item pertama.


Seseorang bertanya apa yang saya coba lakukan. Saya telah menulis sebuah fungsi yang mengeksekusi query SQL dan menghasilkan hasilnya. Terkadang ketika saya memanggil fungsi ini, saya hanya ingin tahu apakah kueri mengembalikan sesuatu dan membuat keputusan berdasarkan itu.


2
Juga masalah dengan kode itu adalah Anda tidak dapat mengemasnya menjadi sebuah fungsi, karena ia akan memakan elemen pertama. Pertanyaan bagus.
andrewrk

2
Dalam kasus saya, saya tidak membutuhkan elemen sama sekali, saya hanya ingin tahu setidaknya ada satu elemen.
Bastien Léonard

2
hah! Kasus penggunaan saya yang sama dalam mencoba menemukan solusi yang sama!
Daniel


Jawaban:


134

anytidak akan melampaui elemen pertama jika True. Jika iterator menghasilkan sesuatu yang salah, Anda dapat menulis any(True for _ in iterator).


Ini sepertinya berhasil untuk saya, dengan re.finditer. Anda dapat menguji bahwa setiap pemberhentian pada kesuksesan pertama dengan mudah: jalankan any((x > 100 for x in xrange(10000000)))dan kemudian jalankan any((x > 10000000 for x in xrange(100000000)))- yang kedua akan memakan waktu lebih lama.
chbrown

1
Ini bekerja untuk kasus "setidaknya x"sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Dave Butler

11
Demikian pula jika Anda perlu memeriksa apakah iterator kosong, yang bisa digunakan all(False for _ in iterator)akan memeriksa apakah iterator kosong. (semua mengembalikan True jika iterator kosong, jika tidak maka berhenti ketika melihat elemen False pertama)
KGardevoir

22
Masalah besar dengan solusi ini adalah Anda tidak dapat benar-benar menggunakan nilai yang dikembalikan dari iterator jika tidak kosong, bukan?
Ken Williams

42

Dalam Python 2.6+, jika nama sentinelterikat pada nilai yang tidak mungkin dihasilkan oleh iterator,

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

Jika Anda tidak tahu apa yang mungkin dihasilkan oleh iterator, buatlah sentinel Anda sendiri (misalnya di bagian atas modul Anda) dengan

sentinel = object()

Jika tidak, Anda dapat menggunakan, dalam peran sentinel, nilai apa pun yang "Anda ketahui" (berdasarkan pertimbangan aplikasi) yang tidak mungkin dihasilkan oleh iterator.


1
Bagus! Untuk kasus penggunaan saya, if not next(iterator, None):sudah cukup karena saya yakin Tidak Ada tidak akan menjadi bagian dari item. Terima kasih telah mengarahkan saya ke arah yang benar!
wasabigeek

1
@wasabi Ingatlah bahwa notakan mengembalikan True untuk objek falsy apa pun, seperti daftar kosong, False, dan nol. is not Nonelebih aman, dan menurut saya lebih jelas.
Caagr98

21

Ini tidak benar-benar lebih bersih, tetapi ini menunjukkan cara untuk mengemasnya dalam fungsi tanpa kehilangan:

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

Ini tidak benar-benar pythonic, dan untuk kasus tertentu, mungkin ada solusi yang lebih baik (tapi kurang umum), seperti default berikutnya .

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

Ini tidak umum karena Tidak Ada dapat menjadi elemen yang valid di banyak iterable.


Ini mungkin cara terbaik untuk melakukan ini. Namun, akan membantu untuk mengetahui apa yang OP coba lakukan? Mungkin ada solusi yang lebih elegan (ini IS Python, bagaimanapun juga).
rossipedia

Terima kasih, saya pikir saya akan menggunakan next().
Bastien Léonard

1
@Bastien, baiklah, tetapi lakukan dengan sentinel yang sesuai (lihat jawaban saya).
Alex Martelli

3
Ada kebocoran memori yang sangat besar dalam solusi ini. Alat-alat teedalam harus menyimpan setiap elemen dari iterator asli jika any_checkperlu maju. Ini lebih buruk daripada hanya mengubah iterator asli menjadi sebuah daftar.
Rafał Dowgird

1
@ RafałDowgird Ini lebih buruk dari sekedar mengubah iterator asli menjadi sebuah daftar. Tidak juga - pikirkan tentang urutan tak terbatas.
Piotr Dobrogost

6

kamu bisa memakai:

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

tapi itu agak tidak jelas untuk pembaca kode


2
.. (Anda dapat menggunakan 1-item yang dapat diulang daripada [Tidak Ada])
mykhal

5

Cara terbaik untuk melakukannya adalah dengan peekabledari more_itertools.

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

Berhati-hatilah jika Anda menyimpan referensi ke iterator lama, iterator itu akan menjadi maju. Anda harus menggunakan iterator baru yang dapat diintip sejak saat itu. Sungguh, meskipun, peekable berharap menjadi satu-satunya kode yang memodifikasi iterator lama itu, jadi Anda tidak boleh menyimpan referensi ke iterator lama yang ada di sekitar.


3

Bagaimana dengan:

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
Yang menarik! Tetapi bagaimana jika next () mengembalikan False karena itulah yang benar-benar dihasilkan?
Bastien Léonard

@ BastienLéonard Buat kelas class NotSet: pass, lalu periksaif next(i, NotSet) is NotSet: print("Iterator is empty")
Elijas

-1

__length_hint__ memperkirakan panjang list(it)- itu metode pribadi, meskipun:

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
tidak dijamin untuk setiap iterator. >>> def it (): ... hasil 1 ... hasil 2 ... hasil 3 ... >>> i = it () >>> i .__ length_hint__ Traceback (panggilan terakhir terakhir): File " <stdin> ", baris 1, di <module> AttributeError: objek 'generator' tidak memiliki atribut ' length_hint '
andrewrk

3
Mungkin juga legal untuk mengembalikan 0 untuk iterator yang memiliki lebih dari nol entri, karena itu hanya petunjuk.
Glenn Maynard

-1

Ini adalah pembungkus iterator berlebihan yang umumnya memungkinkan untuk memeriksa apakah ada item berikutnya (melalui konversi ke boolean). Tentu sangat tidak efisien.

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)

Keluaran:

False
[]
True
[1, 2, 3]

-2

Sedikit terlambat, tapi ... Anda dapat mengubah iterator menjadi daftar dan kemudian bekerja dengan daftar itu:

# 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
Ini tidak efisien karena menghitung seluruh daftar. Tidak akan berfungsi untuk generator tanpa batas.
becko

@becko: Setuju. Tetapi tampaknya tidak demikian pada pertanyaan awal.
Jens

3
Masalah lainnya adalah iterator dapat menghasilkan objek dalam jumlah tak terbatas yang dapat mengakibatkan overflow memori, dan fakta bahwa program tidak akan pernah mencapai pernyataan berikutnya
Willem Van Onsem
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.