ฟังก์ชัน Python Empty Generator


108

ใน python เราสามารถกำหนดฟังก์ชันตัววนซ้ำได้อย่างง่ายดายโดยใส่คำสำคัญที่ให้ผลตอบแทนในเนื้อความของฟังก์ชันเช่น:

def gen():
    for i in range(100):
        yield i

ฉันจะกำหนดฟังก์ชันเครื่องกำเนิดไฟฟ้าที่ไม่ให้ค่า (สร้างค่า 0) ได้อย่างไรรหัสต่อไปนี้ใช้ไม่ได้เนื่องจาก python ไม่สามารถรู้ได้ว่าควรเป็นเครื่องกำเนิดไฟฟ้าและไม่ใช่ฟังก์ชันปกติ:

def empty():
    pass

ฉันสามารถทำสิ่งที่ชอบ

def empty():
    if False:
        yield None

แต่นั่นจะน่าเกลียดมาก มีวิธีใดที่ดีในการใช้ฟังก์ชันตัววนซ้ำที่ว่างเปล่า

คำตอบ:


142

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

>>> def f():
...     return
...     yield
... 
>>> list(f())
[]

ผมไม่แน่ใจว่ามันเป็นเรื่องที่ดีกว่าสิ่งที่คุณมี - มันก็แทนที่ไม่มี-op ifคำสั่งกับไม่มี-op yieldคำสั่ง แต่มันเป็นสำนวนมากกว่า โปรดทราบว่าการใช้yieldไม่ได้ผล

>>> def f():
...     yield
... 
>>> list(f())
[None]

ทำไมไม่ใช้ iter(()) ?

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

หากคำถามเป็นวิธีที่ดีที่สุดในการสร้างตัววนซ้ำที่ว่างเปล่าคุณอาจเห็นด้วยกับZectbumoเกี่ยวกับการใช้iter(())แทน อย่างไรก็ตามสิ่งสำคัญคือต้องสังเกตiter(())ว่าไม่ได้ส่งคืนฟังก์ชัน! ส่งคืนค่าว่างที่สามารถทำซ้ำได้โดยตรง สมมติว่าคุณกำลังทำงานกับ API ที่คาดว่าจะสามารถเรียกใช้ได้ซึ่งจะส่งคืนการทำซ้ำทุกครั้งที่เรียกเช่นเดียวกับฟังก์ชันเครื่องกำเนิดไฟฟ้าทั่วไป คุณจะต้องทำสิ่งนี้:

def empty():
    return iter(())

(เครดิตควรไปที่ Unutbuเพื่อให้คำตอบรุ่นแรกที่ถูกต้อง)

ตอนนี้คุณอาจพบสิ่งที่ชัดเจนกว่าข้างต้น แต่ฉันสามารถจินตนาการถึงสถานการณ์ที่มันจะชัดเจนน้อยลง พิจารณาตัวอย่างของคำจำกัดความของฟังก์ชันเครื่องกำเนิดไฟฟ้า (ที่สร้างขึ้น) แบบยาวนี้:

def zeros():
    while True:
        yield 0

def ones():
    while True:
        yield 1

...

ในตอนท้ายของรายการยาวนั้นฉันอยากจะเห็นอะไรบางอย่างyieldอยู่ในนั้นเช่นนี้:

def empty():
    return
    yield

หรือใน Python 3.3 ขึ้นไป (ตามที่แนะนำโดยDSM ) สิ่งนี้:

def empty():
    yield from ()

การมีอยู่ของyieldคีย์เวิร์ดทำให้เห็นได้ชัดเจนในภาพรวมสั้น ๆ ว่านี่เป็นเพียงฟังก์ชันตัวสร้างอื่นเหมือนกับฟังก์ชันอื่น ๆ ทั้งหมด ต้องใช้เวลาอีกเล็กน้อยเพื่อดูว่าiter(())เวอร์ชันกำลังทำสิ่งเดียวกัน

มันเป็นความแตกต่างที่เล็กน้อย แต่ฉันคิดว่า yieldตามฟังก์ชั่นพื้นฐานนั้นอ่านง่ายและบำรุงรักษาได้มากกว่า

ดูคำตอบที่ยอดเยี่ยมนี้จากuser3840170ที่ใช้disเพื่อแสดงเหตุผลอื่นว่าทำไมวิธีนี้จึงเป็นที่ต้องการ: มันแสดงคำแนะนำน้อยที่สุดเมื่อรวบรวม


นี่เป็นสิ่งที่ดีกว่าif False: yieldแต่ก็ยังค่อนข้างสับสนสำหรับคนที่ไม่รู้จักรูปแบบนี้
Konstantin Weitz

1
อิ๋วมีอะไรกลับมาบ้าง? itertools.empty()ผมคาดว่าสิ่งที่ต้องการ
Grault

1
@ Jesdisciple ดีreturnหมายถึงสิ่งที่แตกต่างในเครื่องกำเนิดไฟฟ้า breakก็มากขึ้นเช่น
ส่ง

ผมชอบวิธีนี้เพราะมัน (ค่อนข้าง) รัดกุมและมันก็ไม่ได้ทำงานพิเศษใด ๆ Falseเช่นเมื่อเทียบกับ
Pi Marillion

คำตอบของ Unutbu ไม่ใช่ "ฟังก์ชันเครื่องกำเนิดไฟฟ้าที่แท้จริง" ตามที่คุณกล่าวไว้เนื่องจากจะส่งคืนตัวทำซ้ำ
Zectbumo

71
iter(())

คุณไม่ต้องการเครื่องกำเนิดไฟฟ้า พวกมึง!


3
ชอบคำตอบนี้ที่สุดแน่นอน มันรวดเร็วเขียนง่ายรวดเร็วในการดำเนินการและน่าสนใจสำหรับฉันมากกว่าiter([])ความจริงง่ายๆที่()เป็นค่าคงที่ในขณะที่[]อาจสร้างอินสแตนซ์วัตถุรายการใหม่ในหน่วยความจำทุกครั้งที่มีการเรียก
Mumbleskates

2
ทำงานกลับผ่านกระทู้นี้ผมรู้สึกถูกบังคับให้ชี้ให้เห็นว่าถ้าคุณต้องการที่ลดลงในความจริงทดแทนสำหรับการทำงานของเครื่องกำเนิดไฟฟ้าคุณจะต้องเขียนสิ่งที่ต้องการหรือempty = lambda: iter(()) def empty(): return iter(())
ส่ง

หากคุณต้องมีเครื่องกำเนิดไฟฟ้าคุณอาจใช้ (_ for _ in ()) ตามที่คนอื่นแนะนำ
Zectbumo

1
@Zectbumo ที่ยังไม่กำเนิดฟังก์ชั่น มันเป็นเพียงเครื่องกำเนิดไฟฟ้า ฟังก์ชันเครื่องกำเนิดจะส่งคืนเครื่องกำเนิดไฟฟ้าใหม่ทุกครั้งที่เรียกใช้
ส่ง

1
สิ่งนี้จะส่งกลับ a tuple_iteratorแทน a generator. หากคุณมีกรณีที่เครื่องกำเนิดไฟฟ้าของคุณไม่จำเป็นต้องคืนค่าอะไรเลยอย่าใช้คำตอบนี้
Boris

54

Python 3.3 (เพราะฉันyield fromเตะและเพราะ @senderle ขโมยความคิดแรกของฉันไป):

>>> def f():
...     yield from ()
... 
>>> list(f())
[]

แต่ฉันต้องยอมรับว่าฉันมีช่วงเวลาที่ยากลำบากในการหากรณีการใช้งานสำหรับสิ่งนี้ซึ่งiter([])หรือ(x)range(0)ไม่ได้ผลดีเท่ากัน


ฉันชอบไวยากรณ์นี้มาก ผลตอบแทนที่ยอดเยี่ยม!
Konstantin Weitz

2
ผมคิดว่านี่เป็นมากขึ้นอ่านสามเณรกว่าอย่างใดอย่างหนึ่งหรือreturn; yield if False: yield None
ยกเลิก

1
นี่เป็นทางออกที่สง่างามที่สุด
Maksym Ganenko

"แต่ผมต้องยอมรับฉันมีช่วงเวลาที่ยากขึ้นมาพร้อมกับกรณีการใช้งานสำหรับการนี้ที่iter([])หรือ(x)range(0)จะไม่ทำงานอย่างเท่าเทียมกัน." -> ไม่แน่ใจว่า(x)range(0)คืออะไรแต่กรณีการใช้งานอาจเป็นวิธีการที่ตั้งใจจะถูกแทนที่ด้วยเครื่องกำเนิดไฟฟ้าแบบเป่าเต็มในคลาสที่สืบทอดมาบางคลาส เพื่อความสอดคล้องคุณต้องการให้แม้แต่ฐานที่คนอื่นสืบทอดมาเพื่อส่งคืนเครื่องกำเนิดไฟฟ้าเช่นเดียวกับที่แทนที่มัน
Vedran Šego

20

อีกทางเลือกหนึ่งคือ:

(_ for _ in ())

ซึ่งแตกต่างจากตัวเลือกอื่น ๆ Pycharm มองว่าสิ่งนี้สอดคล้องกับคำแนะนำประเภทมาตรฐานที่ใช้สำหรับเครื่องกำเนิดไฟฟ้าเช่นGenerator[str, Any, None]
MichałJabłoński

3

เช่นเดียวกับที่ @senderle กล่าวให้ใช้สิ่งนี้:

def empty():
    return
    yield

ฉันเขียนคำตอบนี้เป็นส่วนใหญ่เพื่อแบ่งปันเหตุผลอื่นสำหรับมัน

เหตุผลหนึ่งในการเลือกวิธีการแก้ปัญหานี้เหนือข้ออื่น ๆ ก็คือมันเหมาะสมที่สุดเท่าที่ล่ามเกี่ยวข้อง

>>> import dis
>>> def empty_yield_from():
...     yield from ()
... 
>>> def empty_iter():
...     return iter(())
... 
>>> def empty_return():
...     return
...     yield
...
>>> def noop():
...     pass
...
>>> dis.dis(empty_yield_from)
  2           0 LOAD_CONST               1 (())
              2 GET_YIELD_FROM_ITER
              4 LOAD_CONST               0 (None)
              6 YIELD_FROM
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE
>>> dis.dis(empty_iter)
  2           0 LOAD_GLOBAL              0 (iter)
              2 LOAD_CONST               1 (())
              4 CALL_FUNCTION            1
              6 RETURN_VALUE
>>> dis.dis(empty_return)
  2           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE
>>> dis.dis(noop)
  2           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

อย่างที่เราเห็นempty_returnมีไบต์โค้ดเหมือนกับฟังก์ชันว่างทั่วไปทุกประการ ส่วนที่เหลือดำเนินการอื่น ๆ อีกหลายอย่างที่ไม่เปลี่ยนแปลงพฤติกรรมเลย ความแตกต่างเพียงอย่างเดียวระหว่างempty_returnและnoopคืออดีตมีการตั้งค่าสถานะเครื่องกำเนิดไฟฟ้า:

>>> dis.show_code(noop)
Name:              noop
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        1
Flags:             OPTIMIZED, NEWLOCALS, NOFREE
Constants:
   0: None
>>> dis.show_code(empty_return)
Name:              empty_return
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        1
Flags:             OPTIMIZED, NEWLOCALS, GENERATOR, NOFREE
Constants:
   0: None

แน่นอนว่าความแข็งแกร่งของอาร์กิวเมนต์นี้ขึ้นอยู่กับการนำ Python ไปใช้งานโดยเฉพาะ ล่ามทางเลือกที่ฉลาดเพียงพออาจสังเกตเห็นว่าการดำเนินการอื่น ๆ ไม่มีประโยชน์และเพิ่มประสิทธิภาพให้ อย่างไรก็ตามแม้ว่าการเพิ่มประสิทธิภาพดังกล่าวจะมีอยู่ แต่พวกเขาต้องการให้ล่ามใช้เวลาในการดำเนินการและเพื่อป้องกันไม่ให้สมมติฐานการเพิ่มประสิทธิภาพถูกทำลายเช่นiterตัวระบุที่ขอบเขตทั่วโลกถูกเด้งกลับไปเป็นอย่างอื่น (แม้ว่าจะเป็นไปได้มากว่าจะบ่งชี้ข้อบกพร่องก็ตาม เกิดขึ้นจริง) ในกรณีที่empty_returnไม่มีอะไรให้ปรับให้เหมาะสมดังนั้นแม้แต่ CPython ที่ค่อนข้างไร้เดียงสาก็จะไม่เสียเวลาไปกับการปลอมแปลงใด ๆ


ดี. คุณช่วยเพิ่มผลลัพธ์yield from ()ด้วยได้ไหม (ดูคำตอบของDSM )
ส่ง

3

ต้องเป็นฟังก์ชันเครื่องกำเนิดไฟฟ้าหรือไม่? ถ้าไม่ล่ะ

def f():
    return iter(())

2

วิธี "มาตรฐาน" ในการสร้างตัววนซ้ำว่างเปล่าดูเหมือนจะเป็น iter ([]) ฉันแนะนำให้สร้าง [] อาร์กิวเมนต์เริ่มต้นให้กับ iter (); สิ่งนี้ถูกปฏิเสธด้วยข้อโต้แย้งที่ดีโปรดดูที่http://bugs.python.org/issue25215 - Jurjen


0
generator = (item for item in [])

0

ฉันต้องการยกตัวอย่างตามชั้นเรียนเนื่องจากเรายังไม่มีคำแนะนำใด ๆ นี่คือตัววนซ้ำที่เรียกได้ซึ่งไม่สร้างรายการใด ๆ ฉันเชื่อว่านี่เป็นวิธีที่ตรงไปตรงมาและอธิบายได้ชัดเจนในการแก้ปัญหา

class EmptyGenerator:
    def __iter__(self):
        return self
    def __next__(self):
        raise StopIteration

>>> list(EmptyGenerator())
[]

คุณช่วยเพิ่มคำอธิบายได้ไหมว่าเหตุใด / วิธีนี้จึงแก้ปัญหาของ OP ได้หรือไม่
SherylHohman

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