ผลตอบแทนหรือผลตอบแทนจากฟังก์ชั่นที่เรียกกำเนิด?


30

ฉันมีตัวกำเนิดgeneratorและวิธีการอำนวยความสะดวกให้กับมัน - generate_all.

def generator(some_list):
  for i in some_list:
    yield do_something(i)

def generate_all():
  some_list = get_the_list()
  return generator(some_list) # <-- Is this supposed to be return or yield?

ควรgenerate_all returnหรือyield? ฉันต้องการให้ผู้ใช้ทั้งสองวิธีใช้เช่นกัน

for x in generate_all()

ควรเท่ากับ

some_list = get_the_list()
for x in generate(some_list)

2
มีเหตุผลที่จะใช้อย่างใดอย่างหนึ่ง สำหรับตัวอย่างนี้การกลับมามีประสิทธิภาพมากขึ้น
นักฟิสิกส์บ้า

1
นี้ทำให้ผมนึกถึงคำถามที่คล้ายกันผมเคยโพสต์: “อัตราผลตอบแทนจาก iterable” VS“กลับเราเตอร์ (iterable)” ในขณะที่ไม่เฉพาะเจาะจงเกี่ยวกับเครื่องกำเนิดไฟฟ้ามันเป็นพื้นเดียวกันกับเครื่องกำเนิดไฟฟ้าและตัววนซ้ำจะค่อนข้างคล้ายกันในหลาม นอกจากนี้กลยุทธ์การเปรียบเทียบไบต์ตามที่เสนอโดยคำตอบอาจมีการใช้ที่นี่
PeterE

คำตอบ:


12

เครื่องกำเนิดไฟฟ้าจะทำการประเมินแบบขี้เกียจดังนั้นreturnหรือyieldจะทำงานแตกต่างกันเมื่อคุณทำการดีบั๊กโค้ดของคุณหรือถ้ามีข้อผิดพลาดเกิดขึ้น

ด้วยreturnข้อยกเว้นใด ๆ ที่เกิดขึ้นกับคุณคุณgeneratorจะไม่รู้อะไรเลยgenerate_allนั่นเป็นเพราะเมื่อgeneratorมีการดำเนินการจริง ๆ คุณได้ออกจากgenerate_allฟังก์ชันไปแล้ว ด้วยyieldในนั้นมันจะมีgenerate_allในการย้อนกลับ

def generator(some_list):
    for i in some_list:
        raise Exception('exception happened :-)')
        yield i

def generate_all():
    some_list = [1,2,3]
    return generator(some_list)

for item in generate_all():
    ...
Exception                                 Traceback (most recent call last)
<ipython-input-3-b19085eab3e1> in <module>
      8     return generator(some_list)
      9 
---> 10 for item in generate_all():
     11     ...

<ipython-input-3-b19085eab3e1> in generator(some_list)
      1 def generator(some_list):
      2     for i in some_list:
----> 3         raise Exception('exception happened :-)')
      4         yield i
      5 

Exception: exception happened :-)

และถ้ามันใช้yield from:

def generate_all():
    some_list = [1,2,3]
    yield from generator(some_list)

for item in generate_all():
    ...
Exception                                 Traceback (most recent call last)
<ipython-input-4-be322887df35> in <module>
      8     yield from generator(some_list)
      9 
---> 10 for item in generate_all():
     11     ...

<ipython-input-4-be322887df35> in generate_all()
      6 def generate_all():
      7     some_list = [1,2,3]
----> 8     yield from generator(some_list)
      9 
     10 for item in generate_all():

<ipython-input-4-be322887df35> in generator(some_list)
      1 def generator(some_list):
      2     for i in some_list:
----> 3         raise Exception('exception happened :-)')
      4         yield i
      5 

Exception: exception happened :-)

อย่างไรก็ตามสิ่งนี้มาพร้อมกับราคา เลเยอร์ตัวสร้างเพิ่มเติมมีค่าใช้จ่ายบางส่วน ดังนั้นreturnโดยทั่วไปจะเร็วกว่าyield from ...(หรือfor item in ...: yield item) เล็กน้อย ในกรณีส่วนใหญ่สิ่งนี้จะไม่สำคัญมากนักเพราะสิ่งที่คุณทำในเครื่องกำเนิดไฟฟ้ามักจะควบคุมเวลาทำงานเพื่อให้เลเยอร์เพิ่มเติมไม่สามารถสังเกตเห็นได้

อย่างไรก็ตามyieldมีข้อได้เปรียบเพิ่มเติมบางประการ: คุณไม่ได้ จำกัด การทำซ้ำได้เพียงครั้งเดียวคุณยังสามารถเพิ่มรายการเพิ่มเติมได้อย่างง่ายดาย:

def generator(some_list):
    for i in some_list:
        yield i

def generate_all():
    some_list = [1,2,3]
    yield 'start'
    yield from generator(some_list)
    yield 'end'

for item in generate_all():
    print(item)
start
1
2
3
end

ในกรณีของคุณการดำเนินการค่อนข้างง่ายและฉันไม่ทราบว่าจำเป็นต้องสร้างฟังก์ชั่นหลายอย่างสำหรับสิ่งนี้หรือไม่เราสามารถใช้ built-in mapหรือ expression ของเครื่องแทนได้อย่างง่ายดาย:

map(do_something, get_the_list())          # map
(do_something(i) for i in get_the_list())  # generator expression

ทั้งสองควรเหมือนกัน (ยกเว้นความแตกต่างบางอย่างเมื่อใช้ข้อยกเว้น) และหากพวกเขาต้องการชื่อที่มีความหมายมากกว่านี้คุณก็สามารถสรุปได้ในฟังก์ชันเดียว

มีผู้ช่วยหลายคนที่รวมการดำเนินการทั่วไปไว้ใน iterables ในตัวและสามารถพบได้ในitertoolsโมดูลในตัว ในกรณีที่เรียบง่ายเช่นนี้ฉันจะหันไปใช้สิ่งเหล่านี้และเฉพาะกรณีที่ไม่สำคัญเขียนเครื่องปั่นไฟของคุณเอง

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


17

คุณอาจกำลังมองหาGenerator Delegation (PEP380)

สำหรับตัววนซ้ำแบบง่ายนั้นyield from iterableเป็นเพียงรูปแบบย่อของfor item in iterable: yield item

def generator(iterable):
  for i in iterable:
    yield do_something(i)

def generate_all():
  yield from generator(get_the_list())

มันค่อนข้างกระชับและยังมีข้อได้เปรียบอื่น ๆ อีกมากมายเช่นความสามารถในการเชื่อมโยง iterables โดยพลการ / แตกต่างกัน!


โอ้คุณหมายถึงการตั้งชื่อของlist? มันเป็นตัวอย่างที่ไม่ดีไม่ใช่รหัสจริงที่วางในคำถามฉันน่าจะแก้ไขได้
hyankov

ใช่ - ไม่ต้องกลัวฉันมีความผิดในโค้ดตัวอย่างที่ไม่แม้แต่จะถามตอนแรก ..
ti7

2
อันแรกก็เป็นซับได้เช่นกัน :) yield from map(do_something, iterable)หรือแม้กระทั่งyield from (do_something(x) for x in iterable)
นักฟิสิกส์บ้า

2
"มันเป็นตัวอย่างโค้ดตลอดทาง!"
ti7

3
คุณจะต้องมีการมอบหมายถ้าคุณเป็นตัวคุณเองทำสิ่งอื่นนอกเหนือจากเพียงแค่คืนค่าตัวสร้างใหม่ หากคุณเพิ่งกลับมาสร้างใหม่ไม่จำเป็นต้องมีการมอบหมาย ดังนั้นyield fromจะไม่มีจุดหมายเว้นแต่เสื้อคลุมของคุณทำอย่างอื่น generator-y
ShadowRanger

14

return generator(list)ทำในสิ่งที่คุณต้องการ แต่ทราบว่า

yield from generator(list)

จะเทียบเท่า แต่มีโอกาสที่จะให้ค่ามากขึ้นหลังจากgeneratorหมด ตัวอย่างเช่น:

def generator_all_and_then_some():
    list = get_the_list()
    yield from generator(list)
    yield "one last thing"

5
ฉันเชื่อว่ามีความแตกต่างเล็กน้อยระหว่างyield fromและreturnเมื่อผู้บริโภคของเครื่องกำเนิดไฟฟ้าthrowsมีข้อยกเว้นภายใน - และการดำเนินการอื่น ๆ ที่ได้รับอิทธิพลจากการติดตามสแต็ก
โลกใต้

9

คำสั่งสองข้อความต่อไปนี้จะปรากฏเป็นฟังก์ชันที่เทียบเท่าในกรณีนี้

return generator(list)

และ

yield from generator(list)

ในภายหลังมีค่าประมาณเท่ากับ

for i in generator(list):
    yield i

returnคำสั่งส่งกลับกำเนิดที่คุณกำลังมองหา yield fromหรือyieldคำสั่งเปลี่ยนฟังก์ชั่นของคุณทั้งหมดเป็นสิ่งที่ผลตอบแทนกำเนิดซึ่งผ่านคนที่คุณกำลังมองหา

จากมุมมองของผู้ใช้ไม่มีความแตกต่าง ภายในอย่างไรก็ตามreturnเนื้อหานั้นมีประสิทธิภาพมากกว่าเนื่องจากไม่ได้ห่อหุ้มgenerator(list)ไว้ในเครื่องกำเนิดไฟฟ้าพาส - ทรูที่ฟุ่มเฟือย หากคุณวางแผนที่จะทำการประมวลผลใด ๆกับองค์ประกอบของเครื่องกำเนิดไฟฟ้าที่ห่อให้ใช้รูปแบบyieldของหลักสูตร


4

คุณจะ returnมัน

yieldไอเอ็นจี * จะทำให้ generate_all()การประเมินกับตัวสร้างเองและการเรียกnextใช้ตัวสร้างภายนอกนั้นจะคืนค่าตัวสร้างภายในที่ส่งคืนโดยฟังก์ชันแรกซึ่งไม่ใช่สิ่งที่คุณต้องการ

* ไม่รวม yield from

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