Bagaimana cara memilih hanya satu item dari generator?

214

Saya memiliki fungsi generator seperti berikut:

def myfunct():
  ...
  yield result

Cara biasa memanggil fungsi ini adalah:

for r in myfunct():
  dostuff(r)

Pertanyaan saya, apakah ada cara untuk mendapatkan hanya satu elemen dari generator kapan saja saya suka? Misalnya, saya ingin melakukan sesuatu seperti:

while True:
  ...
  if something:
      my_element = pick_just_one_element(myfunct())
      dostuff(my_element)
  ...
Alexandros
sumber

Jawaban:

304

Buat generator menggunakan

g = myfunct()

Setiap kali Anda menginginkan item, gunakan

next(g)

(atau g.next()dengan Python 2.5 atau di bawah).

Jika generator keluar, ia akan naik StopIteration. Anda bisa menangkap pengecualian ini jika perlu, atau menggunakan defaultargumen untuk next():

next(g, default_value)
Sven Marnach
sumber
4
Catatan, itu hanya akan meningkatkan StopIteration ketika Anda mencoba menggunakan g.next () setelah item terakhir di g telah disediakan.
Wilduck
26
next(gen, default)dapat juga digunakan untuk menghindari StopIterationpengecualian. Misalnya next(g, None)untuk generator string akan menghasilkan string atau Tidak ada setelah iterasi selesai.
Attila
8
dalam Python 3000, next () adalah __next __ ()
Jonathan Baldwin
27
@JonathanBaldwin: Komentar Anda agak menyesatkan. Di Python 3, Anda akan menggunakan sintaks kedua yang diberikan dalam jawaban saya next(g),. Ini secara internal akan memanggil g.__next__(), tetapi Anda tidak benar-benar perlu khawatir tentang hal itu, sama seperti Anda biasanya tidak peduli bahwa len(a)panggilan internal a.__len__().
Sven Marnach
14
Seharusnya aku lebih jelas. g.next()ada g.__next__()di py3k. Builtin next(iterator)sudah ada sejak Python 2.6, dan itulah yang harus digunakan di semua kode Python baru, dan itu sepele untuk backimplement jika Anda perlu mendukung py <= 2.5.
Jonathan Baldwin
29

Untuk memilih hanya satu elemen penggunaan generator breakdalam forpernyataan, ataulist(itertools.islice(gen, 1))

Menurut contoh Anda (secara harfiah) Anda dapat melakukan sesuatu seperti:

while True:
  ...
  if something:
      for my_element in myfunct():
          dostuff(my_element)
          break
      else:
          do_generator_empty()

Jika Anda ingin " dapatkan hanya satu elemen dari generator [yang pernah dihasilkan] kapan pun saya suka " (saya kira 50% itulah niat asli, dan niat paling umum) maka:

gen = myfunct()
while True:
  ...
  if something:
      for my_element in gen:
          dostuff(my_element)
          break
      else:
          do_generator_empty()

Dengan cara ini penggunaan eksplisit generator.next()dapat dihindari, dan penanganan akhir input tidak memerlukan penanganan (samar) StopIterationpengecualian atau perbandingan nilai standar ekstra.

The else:dari forbagian pernyataan hanya diperlukan jika Anda ingin melakukan sesuatu yang istimewa dalam kasus akhir-of-generator.

Catatan pada next()/ .next():

Dalam Python3 .next()metode ini berganti nama menjadi .__next__()untuk alasan yang baik: itu dianggap tingkat rendah (PEP 3114). Sebelum Python 2.6, fungsi bawaan next()tidak ada. Dan itu bahkan dibahas untuk pindah next()ke operatormodul (yang seharusnya bijaksana), karena kebutuhannya yang langka dan inflasi yang meragukan dari nama-nama asli.

Menggunakan next()tanpa default masih merupakan praktik tingkat sangat rendah - melempar crypticStopIteration seperti baut keluar dari biru dalam kode aplikasi normal secara terbuka. Dan menggunakan next()dengan sentinel default - yang terbaik harus menjadi satu-satunya pilihan untuk next()langsung masuk builtins- terbatas dan sering memberikan alasan untuk logika / keterbacaan non-pythonic.

Intinya: Menggunakan next () harus sangat jarang - seperti menggunakan fungsi operatormodul. Menggunakan for x in iterator, islice, list(iterator)dan fungsi lainnya menerima iterator mulus adalah cara alami menggunakan iterator pada level aplikasi - dan cukup selalu mungkin. next()adalah level rendah, konsep tambahan, tidak terlihat - seperti yang diperlihatkan oleh pertanyaan dari utas ini. Sedangkan misalnya menggunakan breakdalam foradalah konvensional.

kxr
sumber
8
Ini terlalu banyak bekerja hanya untuk mendapatkan elemen pertama dari hasil daftar. Cukup sering saya tidak perlu malas tapi tidak punya pilihan di py3. Apakah tidak ada yang serupa mySeq.head?
javadba
2

Saya tidak percaya ada cara mudah untuk mengambil nilai sewenang-wenang dari generator. Generator akan menyediakan metode () berikutnya untuk melintasi sendiri, tetapi urutan penuh tidak segera diproduksi untuk menghemat memori. Itulah perbedaan fungsional antara generator dan daftar.

gddc
sumber
1

Bagi Anda yang memindai jawaban ini untuk contoh kerja lengkap untuk Python3 ... baik di sini ya pergi:

def numgen():
    x = 1000
    while True:
        x += 1
        yield x

nums = numgen() # because it must be the _same_ generator

for n in range(3):
    numnext = next(nums)
    print(numnext)

Output ini:

1001
1002
1003
Dave Rove
sumber
1

Generator adalah fungsi yang menghasilkan iterator. Oleh karena itu, setelah Anda memiliki instance iterator, gunakan next () untuk mengambil item berikutnya dari iterator. Sebagai contoh, gunakan fungsi next () untuk mengambil item pertama, dan kemudian gunakan for inuntuk memproses item yang tersisa:

# create new instance of iterator by calling a generator function
items = generator_function()

# fetch and print first item
first = next(items)
print('first item:', first)

# process remaining items:
for item in items:
    print('next item:', item)
andrii
sumber
0
generator = myfunct()
while True:
   my_element = generator.next()

pastikan untuk menangkap pengecualian yang dilemparkan setelah elemen terakhir diambil

John
sumber
Tidak berlaku untuk Python 3, lihat jawaban yang bagus oleh kxr .
clacke
2
Cukup ganti "generator.next ()" dengan "next (generator)" untuk Python 3.
iyop45
-3

Saya percaya satu-satunya cara adalah mendapatkan daftar dari iterator kemudian mendapatkan elemen yang Anda inginkan dari daftar itu.

l = list(myfunct())
l[4]
keegan3d
sumber
Jawaban Sven mungkin lebih baik, tetapi saya akan membiarkan ini di sini jika itu lebih sesuai dengan kebutuhan Anda.
keegan3d
26
Pastikan Anda memiliki generator yang terbatas sebelum melakukan ini.
Seth
6
Maaf, ini memiliki kompleksitas panjang iterator, sementara masalahnya jelas O (1).
yo
1
Membuang terlalu banyak memori dan proses menyedot dari generator! Juga, seperti yang disebutkan @Seth sebelumnya, generator tidak dijamin kapan berhenti menghasilkan.
pylover
Ini jelas bukan satu-satunya cara (atau cara terbaik jika myfunct()menghasilkan sejumlah besar nilai) karena Anda dapat menggunakan fungsi bawaan nextuntuk mendapatkan nilai yang dihasilkan selanjutnya.
HelloGoodbye