ความแตกต่างระหว่าง Python Generators กับ Iterators


538

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

คำตอบ:


543

iteratorเป็นแนวคิดที่กว้างขึ้น: วัตถุใด ๆ ที่มีระดับมีnextวิธี ( __next__ในหลาม 3) และวิธีการที่ไม่__iter__return self

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

คุณอาจต้องการใช้ตัววนซ้ำแบบกำหนดเองแทนตัวสร้างเมื่อคุณต้องการคลาสที่มีพฤติกรรมการดูแลรักษาสถานะที่ค่อนข้างซับซ้อนหรือต้องการแสดงวิธีอื่นนอกเหนือจากnext(และ__iter__และ__init__) บ่อยครั้งที่ตัวสร้าง (บางครั้งสำหรับความต้องการที่ง่ายพอตัวแสดงออกของตัวสร้าง ) นั้นเพียงพอและมันง่ายกว่าในการเขียนโค้ดเนื่องจากการบำรุงรักษาของรัฐ (ภายในขอบเขตที่สมเหตุสมผล) นั้นโดยทั่วไปแล้ว

ตัวอย่างเช่นเครื่องกำเนิดไฟฟ้าเช่น:

def squares(start, stop):
    for i in range(start, stop):
        yield i * i

generator = squares(a, b)

หรือนิพจน์เครื่องกำเนิดไฟฟ้าที่เทียบเท่า (genexp)

generator = (i*i for i in range(a, b))

จะใช้รหัสเพิ่มเติมเพื่อสร้างเป็นตัววนซ้ำแบบกำหนดเอง:

class Squares(object):
    def __init__(self, start, stop):
       self.start = start
       self.stop = stop
    def __iter__(self): return self
    def next(self): # __next__ in Python 3
       if self.start >= self.stop:
           raise StopIteration
       current = self.start * self.start
       self.start += 1
       return current

iterator = Squares(a, b)

แต่แน่นอนว่าในชั้นเรียนSquaresคุณสามารถเสนอวิธีการพิเศษเช่น

    def current(self):
       return self.start

หากคุณมีความต้องการที่แท้จริงสำหรับฟังก์ชันพิเศษดังกล่าวในแอปพลิเคชันของคุณ


ฉันจะใช้ตัววนซ้ำได้อย่างไรเมื่อฉันสร้างขึ้นแล้ว?
Vincenzooo

@ Vinenzooo ที่ขึ้นอยู่กับสิ่งที่คุณต้องการจะทำกับมัน มันอาจจะเป็นส่วนหนึ่งของfor ... in ...:ส่งผ่านไปยังฟังก์ชั่นหรือคุณจะโทรiter.next()
Caleth

@ Caleth ฉันถามเกี่ยวกับไวยากรณ์ที่แน่นอนเพราะฉันได้รับข้อผิดพลาดในการพยายามใช้for..inไวยากรณ์ บางทีฉันอาจจะหายไปบางสิ่งบางอย่าง แต่เมื่อไม่นานมานี้ฉันไม่จำถ้าฉันแก้ไข ขอบคุณ!
Vincenzooo

136

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

โดยสรุป: Iterators เป็นวัตถุที่มี__iter__และ__next__( nextใน Python 2) วิธีการ เครื่องกำเนิดไฟฟ้าให้วิธีการสร้างอินสแตนซ์ของ Iterators ที่ง่ายและมีอยู่แล้วภายใน

ฟังก์ชั่นที่มีอัตราผลตอบแทนในมันยังคงเป็นฟังก์ชั่นที่เมื่อถูกเรียกคืนค่าอินสแตนซ์ของวัตถุเครื่องกำเนิด

def a_function():
    "when called, returns generator object"
    yield

นิพจน์ตัวสร้างยังส่งคืนตัวสร้าง:

a_generator = (i for i in range(0))

สำหรับการอธิบายและตัวอย่างเชิงลึกเพิ่มเติมโปรดอ่านต่อไป

เครื่องกำเนิดเป็น Iterator

เครื่องกำเนิดเป็นประเภทย่อยของตัววนซ้ำ

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

เราสามารถสร้างเครื่องกำเนิดไฟฟ้าได้หลายวิธี วิธีที่ใช้กันทั่วไปและง่ายมากคือการใช้ฟังก์ชัน

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

>>> def a_function():
        "just a function definition with yield in it"
        yield
>>> type(a_function)
<class 'function'>
>>> a_generator = a_function()  # when called
>>> type(a_generator)           # returns a generator
<class 'generator'>

และเครื่องกำเนิดอีกครั้งคือ Iterator:

>>> isinstance(a_generator, collections.Iterator)
True

Iterator เป็น Iterable

Iterator เป็น Iterable

>>> issubclass(collections.Iterator, collections.Iterable)
True

ซึ่งต้องใช้__iter__วิธีการที่ส่งกลับ Iterator:

>>> collections.Iterable()
Traceback (most recent call last):
  File "<pyshell#79>", line 1, in <module>
    collections.Iterable()
TypeError: Can't instantiate abstract class Iterable with abstract methods __iter__

ตัวอย่างของ iterables คือ tuples ในตัว, รายการ, พจนานุกรม, ชุด, ชุดตรึง, สตริง, สตริงไบต์, อาร์เรย์ไบต์, ช่วงและ memoryviews:

>>> all(isinstance(element, collections.Iterable) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

การทำซ้ำต้องมีnextหรือ__next__วิธีการ

ใน Python 2:

>>> collections.Iterator()
Traceback (most recent call last):
  File "<pyshell#80>", line 1, in <module>
    collections.Iterator()
TypeError: Can't instantiate abstract class Iterator with abstract methods next

และใน Python 3:

>>> collections.Iterator()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Iterator with abstract methods __next__

เราสามารถรับตัววนซ้ำจากวัตถุในตัว (หรือวัตถุที่กำหนดเอง) ด้วยiterฟังก์ชัน:

>>> all(isinstance(iter(element), collections.Iterator) for element in (
        (), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True

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

จากเอกสารประกอบ

จากส่วนตัวสร้างประเภทของส่วนประเภทตัววนซ้ำของเอกสารประกอบชนิดในตัว:

เครื่องกำเนิดไฟฟ้าของ Python ให้วิธีที่สะดวกในการใช้โปรโตคอลตัววนซ้ำ หากมี__iter__()การใช้วิธีของคอนเทนเนอร์วัตถุเป็นตัวสร้างมันจะส่งคืนวัตถุตัววนซ้ำโดยอัตโนมัติ (ในทางเทคนิควัตถุตัวสร้าง) จะจัดหาเมธอด__iter__()และnext()[ __next__()ใน Python 3] ข้อมูลเพิ่มเติมเกี่ยวกับเครื่องกำเนิดไฟฟ้าสามารถพบได้ในเอกสารสำหรับการแสดงออกของผลผลิต

(เพิ่มการเน้น)

ดังนั้นจากนี้เราเรียนรู้ว่าเครื่องกำเนิดไฟฟ้าเป็นประเภท Iterator (สะดวก)

ตัวอย่างวัตถุตัววนซ้ำ

คุณอาจสร้างวัตถุที่ใช้โปรโตคอล Iterator โดยการสร้างหรือขยายวัตถุของคุณเอง

class Yes(collections.Iterator):

    def __init__(self, stop):
        self.x = 0
        self.stop = stop

    def __iter__(self):
        return self

    def next(self):
        if self.x < self.stop:
            self.x += 1
            return 'yes'
        else:
            # Iterators must raise when done, else considered broken
            raise StopIteration

    __next__ = next # Python 3 compatibility

แต่ง่ายกว่าที่จะใช้ตัวสร้างเพื่อทำสิ่งนี้:

def yes(stop):
    for _ in range(stop):
        yield 'yes'

หรืออาจจะง่ายกว่า Generator Expression (ทำงานคล้ายกับ list comprehensions):

yes_expr = ('yes' for _ in range(stop))

พวกเขาทั้งหมดสามารถใช้ในลักษณะเดียวกัน:

>>> stop = 4             
>>> for i, y1, y2, y3 in zip(range(stop), Yes(stop), yes(stop), 
                             ('yes' for _ in range(stop))):
...     print('{0}: {1} == {2} == {3}'.format(i, y1, y2, y3))
...     
0: yes == yes == yes
1: yes == yes == yes
2: yes == yes == yes
3: yes == yes == yes

ข้อสรุป

คุณสามารถใช้โปรโตคอล Iterator โดยตรงเมื่อคุณต้องการขยายวัตถุ Python เป็นวัตถุที่สามารถทำซ้ำได้

อย่างไรก็ตามในกรณีส่วนใหญ่คุณจะเหมาะสมที่สุดที่จะใช้yieldเพื่อกำหนดฟังก์ชั่นที่ส่งกลับตัวสร้างตัวส่งสัญญาณหรือพิจารณาตัวสร้างนิพจน์

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


41

iterators:

Iterator เป็นวัตถุที่ใช้next()วิธีการรับค่าลำดับถัดไป

กำเนิด:

เครื่องกำเนิดไฟฟ้าเป็นฟังก์ชันที่สร้างหรือให้ลำดับของค่าโดยใช้yieldวิธีการ

ทุกnext()เมธอดเรียกใช้บนตัวกำเนิดวัตถุ (ตัวอย่างfเช่นในตัวอย่างด้านล่าง) ส่งคืนโดยฟังก์ชันตัวสร้าง (ตัวอย่างเช่น: foo()ฟังก์ชันในตัวอย่างด้านล่าง) สร้างค่าถัดไปตามลำดับ

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

ตัวอย่างต่อไปนี้แสดงให้เห็นถึงการทำงานร่วมกันระหว่างผลตอบแทนและการโทรไปยังวิธีการต่อไปบนวัตถุกำเนิด

>>> def foo():
...     print "begin"
...     for i in range(3):
...         print "before yield", i
...         yield i
...         print "after yield", i
...     print "end"
...
>>> f = foo()
>>> f.next()
begin
before yield 0            # Control is in for loop
0
>>> f.next()
after yield 0             
before yield 1            # Continue for loop
1
>>> f.next()
after yield 1
before yield 2
2
>>> f.next()
after yield 2
end
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

3
เพียงแค่ผลผลิตของ FYI ไม่ใช่วิธีการมันเป็นคำหลัก
Jay Parikh

25

การเพิ่มคำตอบเพราะไม่มีคำตอบที่มีอยู่โดยเฉพาะความสับสนในวรรณคดีอย่างเป็นทางการ

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

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

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

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

แม้จะมีความสับสนทั้งหมดนี้เราสามารถค้นหาการอ้างอิงภาษา Pythonสำหรับคำที่ชัดเจนและสุดท้าย:

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

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

ดังนั้นในการใช้งานอย่างเป็นทางการและแม่นยำ"generator" ไม่มีเงื่อนไขหมายถึงวัตถุตัวกำเนิดไม่ใช่ฟังก์ชันตัวสร้าง

การอ้างอิงข้างต้นใช้สำหรับ Python 2 แต่การอ้างอิงภาษา Python 3นั้นเหมือนกัน อย่างไรก็ตามอภิธานศัพท์ Python 3ระบุว่า

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


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

6
1. ไม่คำนึงถึงเหตุผลทางทฤษฎีว่าทำไมไม่ควรมีความสับสนความคิดเห็นเกี่ยวกับคำตอบอื่น ๆ ของคำถามนี้ปฏิเสธและขัดแย้งกันโดยไม่มีการแก้ไขแสดงความสับสนจริง 2. ความไม่แน่ชัดไม่เป็นทางการนั้นเป็นเรื่องปกติ แต่แหล่งข้อมูลที่แม่นยำและเชื่อถือได้ควรเป็นหนึ่งในตัวเลือกของ SO ฉันใช้ทั้งฟังก์ชันตัวสร้างและวัตถุอย่างกว้างขวางในโครงการปัจจุบันของฉันและความแตกต่างมีความสำคัญมากเมื่อออกแบบและการเข้ารหัส เป็นการดีที่จะรู้ว่าคำศัพท์ใดที่จะใช้ในตอนนี้ดังนั้นฉันจึงไม่ต้องเปลี่ยนชื่อและข้อคิดเห็นหลายสิบตัวในภายหลัง
Paul

2
ลองนึกภาพวรรณคดีคณิตศาสตร์ที่ไม่มีความแตกต่างระหว่างฟังก์ชันและค่าส่งคืน บางครั้งสะดวกที่จะทำให้พวกเขาสับสนอย่างไม่เป็นทางการ แต่เพิ่มความเสี่ยงของความผิดพลาดที่หลากหลาย คณิตศาสตร์สมัยใหม่ขั้นสูงจะถูกขัดขวางอย่างมีนัยสำคัญและไม่จำเป็นหากความแตกต่างไม่เป็นทางการในการประชุมภาษาและสัญลักษณ์
Paul

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

1
@ พอลขอบคุณที่เขียนคำตอบนี้ ความสับสนนี้มีความสำคัญเนื่องจากความแตกต่างระหว่างวัตถุเครื่องกำเนิดไฟฟ้าและฟังก์ชั่นเครื่องกำเนิดคือความแตกต่างระหว่างการรับพฤติกรรมที่ต้องการและต้องค้นหาเครื่องกำเนิดไฟฟ้า
blujay

15

ทุกคนมีคำตอบที่ดีและ verbose กับตัวอย่างและฉันขอบคุณมัน ฉันแค่อยากจะให้คำตอบสั้น ๆ สองสามบรรทัดสำหรับคนที่ยังไม่ชัดเจนในเชิงแนวคิด:

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

หวังว่าชัดเจน


10

คำตอบก่อนหน้าพลาดการเพิ่มนี้: ตัวสร้างมีcloseวิธีในขณะที่ตัววนซ้ำทั่วไปไม่มี closeวิธีการเรียกStopIterationข้อยกเว้นในเครื่องกำเนิดไฟฟ้าซึ่งอาจจะติดอยู่ในfinallyประโยคหนึ่งใน iterator ว่าจะได้รับโอกาสที่จะทำงานบางทำความสะอาด-A สิ่งที่เป็นนามธรรมนี้ทำให้สามารถใช้งานได้ใน iterators ขนาดใหญ่กว่าแบบง่าย หนึ่งสามารถปิดตัวสร้างเป็นหนึ่งสามารถปิดไฟล์โดยไม่ต้องกังวลเกี่ยวกับสิ่งที่อยู่ข้างใต้

ที่กล่าวว่าคำตอบส่วนบุคคลของฉันไปที่คำถามแรกจะเป็น: iteratable มี__iter__เพียงวิธีการเดียว, iterators ทั่วไปมี__next__วิธีเดียวที่กำเนิดมีทั้ง__iter__และและเพิ่มเติม__next__close

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

สำหรับเรื่องโฟลว์การควบคุมเครื่องกำเนิดไฟฟ้าเป็นแนวคิดที่สำคัญมากอย่างที่สัญญาไว้: ทั้งสองเป็นนามธรรมและเรียงความได้


คุณยกตัวอย่างเพื่ออธิบายว่าคุณหมายถึงเมื่อพูดถึงการแต่งเพลงหรือไม่? นอกจากนี้คุณสามารถอธิบายสิ่งที่คุณมีในใจเมื่อพูดถึง "ตัววนซ้ำทั่วไป " ได้หรือไม่
bli

1
คำตอบอื่น ( stackoverflow.com/a/28353158/1878788 ) ระบุว่า "ตัววนซ้ำนั้นซ้ำได้" เนื่องจาก iterable มี__iter__เมธอด iterator จึงมีได้__next__อย่างไร หากพวกเขาควรจะทำซ้ำฉันคาดหวังว่าพวกเขาจะต้องมี__iter__เหมือนกัน
bli

1
@bli: AFAICS คำตอบนี้ที่นี่หมายถึง PEP234 มาตรฐานดังนั้นจึงถูกต้องในขณะที่คำตอบอื่น ๆ หมายถึงการใช้งานบางอย่างดังนั้นจึงเป็นที่น่าสงสัย มาตรฐานจำเป็นต้องมี__iter__ใน iterables เพื่อส่งคืน iterator เท่านั้นซึ่งต้องใช้nextวิธีการ ( __next__ใน Python3) โปรดอย่าสับสนมาตรฐาน (สำหรับการพิมพ์เป็ด) กับการใช้งาน (วิธีการใช้งานล่าม Python โดยเฉพาะ) นี่เป็นความสับสนระหว่างฟังก์ชันตัวสร้าง (นิยาม) และตัวกำเนิดวัตถุ (การนำไปใช้) ;)
Tino

7

ฟังก์ชันตัวสร้าง, วัตถุตัวสร้าง, ตัวสร้าง:

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

<generator object fib at 0x01342480>ในตัวอย่างนี้ผมได้สร้างฟังก์ชั่นเครื่องกำเนิดไฟฟ้าที่ส่งกลับวัตถุปั่นไฟ เช่นเดียวกับตัววนซ้ำอื่น ๆ วัตถุตัวสร้างสามารถใช้ในการ forวนรอบหรือด้วยฟังก์ชันในตัวnext()ซึ่งส่งคืนค่าถัดไปจากตัวสร้าง

def fib(max):
    a, b = 0, 1
    for i in range(max):
        yield a
        a, b = b, a + b
print(fib(10))             #<generator object fib at 0x01342480>

for i in fib(10):
    print(i)               # 0 1 1 2 3 5 8 13 21 34


print(next(myfib))         #0
print(next(myfib))         #1
print(next(myfib))         #1
print(next(myfib))         #2

ดังนั้นฟังก์ชั่นเครื่องกำเนิดไฟฟ้าเป็นวิธีที่ง่ายที่สุดในการสร้างวัตถุ Iterator

ผู้กล่าวซ้ำ :

วัตถุตัวสร้างทุกตัวเป็นตัววนซ้ำแต่ไม่ใช่ในทางกลับกัน วัตถุตัววนซ้ำที่กำหนดเองสามารถสร้างขึ้นได้หากคลาสนั้นใช้ __iter__และ__next__วิธีการ (เรียกอีกอย่างว่าโปรโตคอลตัววนซ้ำ)

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

class Fib:
    def __init__(self,max):
        self.current=0
        self.next=1
        self.max=max
        self.count=0

    def __iter__(self):
        return self

    def __next__(self):
        if self.count>self.max:
            raise StopIteration
        else:
            self.current,self.next=self.next,(self.current+self.next)
            self.count+=1
            return self.next-self.current

    def __str__(self):
        return "Generator object"

itobj=Fib(4)
print(itobj)               #Generator object

for i in Fib(4):  
    print(i)               #0 1 1 2

print(next(itobj))         #0
print(next(itobj))         #1
print(next(itobj))         #1

6

ตัวอย่างจาก Ned Batchelderแนะนำอย่างยิ่งสำหรับตัววนซ้ำและตัวกำเนิด

วิธีการที่ไม่มีเครื่องกำเนิดไฟฟ้าที่ทำบางสิ่งบางอย่างกับตัวเลข

def evens(stream):
   them = []
   for n in stream:
      if n % 2 == 0:
         them.append(n)
   return them

ในขณะที่ใช้เครื่องกำเนิดไฟฟ้า

def evens(stream):
    for n in stream:
        if n % 2 == 0:
            yield n
  • เราไม่ต้องการรายการหรือreturnคำสั่งใด ๆ
  • มีประสิทธิภาพสำหรับสตรีมความยาวขนาดใหญ่ / ไม่มีที่สิ้นสุด ... เพียงแค่เดินและให้คุณค่า

การเรียกใช้evensเมธอด (ตัวสร้าง) เป็นปกติ

num = [...]
for n in evens(num):
   do_smth(n)
  • เครื่องกำเนิดไฟฟ้ายังใช้ในการทำลายวงคู่

iterator

หนังสือที่เต็มไปด้วยหน้าเป็นตัววนซ้ำบุ๊กมาร์กเป็นตัว วนซ้ำ

และบุ๊กมาร์กนี้ไม่ต้องทำอะไรนอกจากย้าย next

litr = iter([1,2,3])
next(litr) ## 1
next(litr) ## 2
next(litr) ## 3
next(litr) ## StopIteration  (Exception) as we got end of the iterator

ในการใช้เครื่องมือสร้าง ... เราต้องการฟังก์ชั่น

ในการใช้ Iterator ... เราต้องการnextและiter

ดังที่ได้กล่าวไว้:

ฟังก์ชั่นเครื่องกำเนิดไฟฟ้าส่งกลับวัตถุตัววนซ้ำ

ประโยชน์ทั้งหมดของ Iterator:

เก็บองค์ประกอบหนึ่งครั้งในหน่วยความจำ


เกี่ยวกับข้อมูลโค้ดแรกของคุณฉันต้องการทราบว่า 'สตรีม' arg อะไรที่อาจเป็นมากกว่ารายการ []
Iqra

5

คุณสามารถเปรียบเทียบทั้งสองวิธีสำหรับข้อมูลเดียวกัน:

def myGeneratorList(n):
    for i in range(n):
        yield i

def myIterableList(n):
    ll = n*[None]
    for i in range(n):
        ll[i] = i
    return ll

# Same values
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
for i1, i2 in zip(ll1, ll2):
    print("{} {}".format(i1, i2))

# Generator can only be read once
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)

print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))

# Generator can be read several times if converted into iterable
ll1 = list(myGeneratorList(10))
ll2 = myIterableList(10)

print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))

นอกจากนี้หากคุณตรวจสอบรอยเท้าหน่วยความจำเครื่องกำเนิดจะใช้หน่วยความจำน้อยลงมากเนื่องจากไม่จำเป็นต้องเก็บค่าทั้งหมดในหน่วยความจำในเวลาเดียวกัน


1

ฉันกำลังเขียนเฉพาะสำหรับมือใหม่ของ Python ในวิธีที่ง่ายมากถึงแม้ว่า Python จะทำสิ่งต่างๆมากมาย

เริ่มต้นด้วยพื้นฐานที่ดีมาก:

พิจารณารายการ

l = [1,2,3]

ลองเขียนฟังก์ชั่นที่เทียบเท่ากัน:

def f():
    return [1,2,3]

o / p ของprint(l): [1,2,3]& o / p ของprint(f()) : [1,2,3]

มาสร้าง list กันเถอะ: ในรายการไพ ธ อนนั้นสามารถทำซ้ำได้ซึ่งหมายความว่าคุณสามารถใช้ตัววนซ้ำได้ทุกเมื่อที่คุณต้องการ

ลองใช้ตัววนซ้ำในรายการ:

iter_l = iter(l) # iterator applied explicitly

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

หมายเหตุ: เครื่องกำเนิดไฟฟ้าทุกตัว iterable เสมอโดยใช้ iterator โดยปริยายและนี่ iterator implicit เป็น crux ดังนั้นฟังก์ชัน generator จะเป็น:

def f():
  yield 1 
  yield 2
  yield 3

iter_f = f() # which is iter(f) as iterator is already applied implicitly

ดังนั้นหากคุณสังเกตเห็นทันทีที่คุณสร้างฟังก์ชั่นตัวสร้างฟฟ้าแล้วมันก็เป็นไปได้ (f)

ตอนนี้

l คือรายการหลังจากใช้วิธี iterator "iter" มันจะกลายเป็น iter (l)

f มีอยู่แล้ว iter (f) หลังจากใช้วิธี iterator "iter" มันจะกลายเป็น iter (iter (f)) ซึ่งเป็น iter อีกครั้ง (f)

มันเป็นสิ่งที่คุณกำลังหล่อ int ถึง int (x) ซึ่งมีอยู่แล้วและจะยังคง int (x)

ตัวอย่างเช่น o / p ของ:

print(type(iter(iter(l))))

คือ

<class 'list_iterator'>

อย่าลืมว่านี่คือ Python ไม่ใช่ C หรือ C ++

ดังนั้นข้อสรุปจากคำอธิบายข้างต้นคือ:

รายการ l ~ = iter (l)

ฟังก์ชันเครื่องกำเนิดไฟฟ้า f == iter (f)

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