วิธีการเลือกเพียงหนึ่งรายการจากเครื่องกำเนิดไฟฟ้า?


213

ฉันมีฟังก์ชั่นเครื่องกำเนิดไฟฟ้าดังนี้:

def myfunct():
  ...
  yield result

วิธีปกติในการเรียกใช้ฟังก์ชันนี้คือ:

for r in myfunct():
  dostuff(r)

คำถามของฉันมีวิธีที่จะได้รับเพียงหนึ่งองค์ประกอบจากเครื่องกำเนิดไฟฟ้าเมื่อใดก็ตามที่ฉันชอบ? ตัวอย่างเช่นฉันต้องการทำสิ่งที่ชอบ:

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

คำตอบ:


304

สร้างตัวสร้างโดยใช้

g = myfunct()

ทุกครั้งที่คุณต้องการรายการใช้

next(g)

(หรือg.next()ใน Python 2.5 หรือต่ำกว่า)

StopIterationหากเครื่องกำเนิดไฟฟ้าออกก็จะเพิ่ม คุณสามารถตรวจจับข้อยกเว้นนี้หากจำเป็นหรือใช้defaultอาร์กิวเมนต์เพื่อnext():

next(g, default_value)

4
หมายเหตุมันจะเพิ่ม StopIteration เมื่อคุณพยายามใช้ g.next () หลังจากรายการสุดท้ายใน g ได้รับการจัดเตรียมแล้ว
Wilduck

26
next(gen, default)อาจถูกใช้เพื่อหลีกเลี่ยงStopIterationข้อยกเว้น ตัวอย่างเช่นnext(g, None)สำหรับตัวสร้างของสตริงจะให้ผลเป็นสตริงหรือไม่มีหลังจากการวนซ้ำเสร็จสิ้น
อัตติลา

8
ใน Python 3000 ถัดไป () คือ __ ถัดไป __ ()
Jonathan Baldwin

27
@JonathanBaldwin: ความคิดเห็นของคุณค่อนข้างเข้าใจผิด ในหลาม 3 next(g)คุณจะใช้ไวยากรณ์ที่สองได้รับในคำตอบของฉัน ภายในนี้จะเรียกg.__next__()แต่คุณไม่ได้จริงๆต้องกังวลเกี่ยวกับที่เดียวกับที่คุณมักจะไม่สนใจว่าภายในสายlen(a) a.__len__()
Sven Marnach

14
ฉันควรจะชัดเจนมากขึ้น g.next()อยู่g.__next__()ใน py3k builtin next(iterator)มีมาตั้งแต่ Python 2.6 และเป็นสิ่งที่ควรใช้ในรหัส Python ใหม่ทั้งหมดและเป็นเรื่องเล็กน้อยที่จะใช้งานแบ็คไลท์ถ้าคุณต้องการสนับสนุน py <= 2.5
Jonathan Baldwin

29

สำหรับการเลือกเพียงหนึ่งองค์ประกอบของเครื่องกำเนิดไฟฟ้าที่ใช้breakในforคำสั่งหรือlist(itertools.islice(gen, 1))

ตามตัวอย่างของคุณ (ตามตัวอักษร) คุณสามารถทำสิ่งที่ชอบ:

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

หากคุณต้องการ " รับเพียงองค์ประกอบหนึ่งจากเครื่องกำเนิด [สร้างครั้งเดียว] เมื่อใดก็ตามที่ฉันต้องการ " (ฉันคิดว่า 50% นั่นคือความตั้งใจเดิมและความตั้งใจที่พบบ่อยที่สุด) จากนั้น:

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

วิธีนี้generator.next()สามารถหลีกเลี่ยงการใช้อย่างชัดเจนและไม่จำเป็นต้องใช้การจัดการปลายทาง (cryptic)StopIterationจัดการข้อยกเว้นหรือการเปรียบเทียบค่าเริ่มต้นพิเศษ

ส่วนelse:ของforstatement นั้นจำเป็นต่อเมื่อคุณต้องการทำสิ่งพิเศษในกรณีที่เครื่องกำเนิดสิ้นสุด

หมายเหตุเกี่ยวกับnext()/ .next():

ใน Python3 .next()วิธีการถูกเปลี่ยนชื่อเป็น.__next__()เหตุผลที่ดี: มันถือว่าระดับต่ำ (PEP 3114) ก่อนหน้า Python 2.6 ฟังก์ชัน builtin next()ไม่มีอยู่ และมันก็ถูกกล่าวถึงเพื่อย้ายnext()ไปยังoperatorโมดูล (ซึ่งจะฉลาด) เนื่องจากความต้องการที่หายากและอัตราเงินเฟ้อที่น่าสงสัยของชื่อในตัว

การใช้งานnext()โดยไม่มีค่าเริ่มต้นนั้นยังคงเป็นวิธีปฏิบัติระดับต่ำมาก - การขว้างลับStopIterationเหมือนสายฟ้าออกจากสีน้ำเงินในรหัสแอปพลิเคชันปกติอย่างเปิดเผย และใช้next()กับ Sentinel ที่เป็นค่าเริ่มต้น - ซึ่งเป็นทางเลือกที่ดีที่สุดสำหรับการเข้าใช้next()โดยตรงbuiltins - ถูก จำกัด และมักจะให้เหตุผลกับตรรกะที่ไม่ใช่ pythonic ตรรกะ / readablity

Bottom line: การใช้ next () ควรหายากมากเช่นการใช้ฟังก์ชันของoperatorโมดูล การใช้for x in iterator, islice, list(iterator)และฟังก์ชั่นอื่น ๆ ยอมรับ iterator ต่อเนื่องเป็นวิธีที่ธรรมชาติของการใช้ iterators ในระดับแอพลิเคชัน - และค่อนข้างเป็นไปได้เสมอ next()อยู่ในระดับต่ำแนวคิดเพิ่มเติมพิเศษไม่เป็นที่ประจักษ์แจ้งตามคำถามของกระทู้นี้ ในขณะที่เช่นใช้breakในforเป็นธรรมดา


8
นี่เป็นวิธีการทำงานที่มากเกินไปเพียงแค่รับองค์ประกอบแรกของรายการผลลัพธ์ บ่อยครั้งที่ฉันไม่ต้องการให้มันขี้เกียจ แต่ไม่มีทางเลือกใน py3 ไม่มีอะไรที่คล้ายกับmySeq.head?
javadba

2

ฉันไม่เชื่อว่ามีวิธีที่สะดวกในการรับค่าตามอำเภอใจจากเครื่องกำเนิดไฟฟ้า เครื่องกำเนิดจะให้วิธีถัดไป () เพื่อสำรวจตัวมันเอง แต่ลำดับเต็มไม่ได้ถูกสร้างขึ้นทันทีเพื่อบันทึกหน่วยความจำ นั่นคือความแตกต่างในการทำงานระหว่างตัวกำเนิดและรายการ


1

สำหรับคนที่คุณอ่านคำตอบเหล่านี้เพื่อเป็นตัวอย่างการทำงานที่สมบูรณ์สำหรับ Python3 ... ดีมากที่นี่คุณไป:

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)

ผลลัพธ์นี้:

1001
1002
1003

1

ตัวสร้างเป็นฟังก์ชันที่สร้างตัววนซ้ำ ดังนั้นเมื่อคุณมีอินสแตนซ์ตัววนซ้ำใช้next ()เพื่อดึงรายการถัดไปจากตัววนซ้ำ ตัวอย่างเช่นใช้ฟังก์ชัน next () เพื่อดึงไอเท็มแรกและใช้ในภายหลังfor inเพื่อประมวลผลรายการที่เหลือ:

# 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)

0
generator = myfunct()
while True:
   my_element = generator.next()

ตรวจสอบให้แน่ใจว่าได้จับข้อยกเว้นที่เกิดขึ้นหลังจากองค์ประกอบสุดท้ายถูกนำมาใช้


ไม่ถูกต้องสำหรับหลาม 3 ดูดีคำตอบโดย kxr
clacke

2
เพียงแค่แทนที่ "generator.next ()" ด้วย "next (generator)" สำหรับ Python 3
iyop45

-3

ฉันเชื่อว่าวิธีเดียวที่จะได้รับรายชื่อจากตัววนซ้ำแล้วรับองค์ประกอบที่คุณต้องการจากรายการนั้น

l = list(myfunct())
l[4]

คำตอบของ Sven น่าจะดีกว่า แต่ฉันจะปล่อยให้เรื่องนี้เพราะมันสอดคล้องกับความต้องการของคุณ
keegan3d

26
ตรวจสอบให้แน่ใจว่าคุณมีเครื่องกำเนิดแน่นอนก่อนที่จะทำเช่นนี้
เซท

6
ขออภัยนี่มีความซับซ้อนของความยาวของตัววนซ้ำในขณะที่ปัญหาคือ O (1)
'21

1
เสียความทรงจำและกระบวนการในการดูดจากเครื่องกำเนิดไฟฟ้ามากเกินไป! เช่นเดียวกับ @Seth ที่กล่าวถึงก่อนหน้านี้เครื่องกำเนิดไฟฟ้าจะไม่รับประกันว่าจะหยุดผลิตเมื่อใด
pylover

เห็นได้ชัดว่านี่ไม่ใช่วิธีเดียว (หรือวิธีที่ดีที่สุดหากmyfunct()สร้างค่าจำนวนมาก) เนื่องจากคุณสามารถใช้ฟังก์ชันในตัวnextเพื่อรับค่าที่สร้างขึ้นถัดไป
HelloGoodbye
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.