簡體   English   中英

Python 的生成器和迭代器的區別

[英]Difference between Python's Generators and Iterators

迭代器和生成器有什么區別? 關於何時使用每種情況的一些示例會很有幫助。

iterator是一個更一般的概念:任何其類具有__next__方法(​​Python 2 中的next )和return self__iter__方法的對象。

每個生成器都是一個迭代器,但反之則不然。 生成器是通過調用具有一個或多個yield表達式(在 Python 2.5 和更早版本中的yield語句)的函數來構建的,並且是一個滿足上一段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 2
       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

如果您在應用程序中對此類額外功能有任何實際需求。

迭代器和生成器有什么區別? 關於何時使用每種情況的一些示例會很有幫助。

總之:迭代器是具有__iter____next__ (Python 2 中的next )方法的對象。 生成器提供了一種簡單的內置方法來創建迭代器的實例。

一個帶有 yield 的函數仍然是一個函數,當被調用時,它會返回一個生成器對象的實例:

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

生成器表達式也返回一個生成器:

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

有關更深入的說明和示例,請繼續閱讀。

生成器迭代器

具體來說,生成器是迭代器的子類型。

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

我們可以通過多種方式創建生成器。 一個非常常見和簡單的方法是使用函數。

具體來說,帶有 yield 的函數是一個函數,它在被調用時會返回一個生成器:

>>> 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'>

生成器又是一個迭代器:

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

迭代器可迭代的

迭代器是可迭代的,

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

這需要一個返回迭代器的__iter__方法:

>>> 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__

可迭代的一些示例是內置元組、列表、字典、集合、凍結集合、字符串、字節字符串、字節數組、范圍和內存視圖:

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

當您嘗試使用帶有 for 循環的對象時,會調用__iter__方法。 然后在迭代器對象上調用__next__方法以將每個項目取出用於循環。 迭代器在你用完它時會引發StopIteration ,此時它不能被重用。

從文檔

從內置類型文檔的迭代器類型部分的生成器類型部分:

Python 的生成器提供了一種方便的方式來實現迭代器協議。 如果容器對象的__iter__()方法被實現為生成器,它將自動返回提供__iter__()next() [Python 3 中的__next__() ] 方法的迭代器對象(技術上是生成器對象)。 有關生成器的更多信息可以在 yield 表達式的文檔中找到。

(強調補充。)

所以從這里我們了解到生成器是一種(方便的)迭代器。

示例迭代器對象

您可以通過創建或擴展您自己的對象來創建實現迭代器協議的對象。

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'

或者更簡單,生成器表達式(與列表推導類似):

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

結論

當需要將 Python 對象擴展為可以迭代的對象時,可以直接使用 Iterator 協議。

但是,在絕大多數情況下,您最適合使用yield來定義返回生成器迭代器的函數或考慮生成器表達式。

最后,請注意,生成器作為協程提供了更多功能。 在我對“yield 關鍵字有什么作用?”的回答中,我深入解釋了生成器以及yield語句。

迭代器是使用next()方法獲取序列的以下值的對象。

生成器是使用yield關鍵字產生或產生一系列值的函數。

生成器函數(例如:下面的foo() )返回的生成器對象(例如:下面的f )上的每個next()方法調用都會生成序列中的下一個值。

當一個生成器函數被調用時,它甚至沒有開始執行該函數就返回一個生成器對象。 next()方法第一次被調用時,函數開始執行,直到它到達返回產生值的yield語句。 yield跟蹤發生了什么,即它記住最后一次執行。 其次, next()調用從前一個值繼續。

以下示例演示了yield和對生成器對象的next方法的調用之間的相互作用。

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

添加答案,因為現有答案都沒有專門解決官方文獻中的混淆。

生成器函數是使用yield而不是return定義的普通函數。 當被調用時,生成器函數返回一個生成器對象,它是一種迭代器——它有一個next()方法。 當您調用next()時,將返回生成器函數產生的下一個值。

根據您閱讀的 Python 源文檔,函數或對象都可以稱為“生成器”。 Python 詞匯表說生成器函數,而Python wiki暗示生成器對象。 Python 教程非常成功地在三個句子的空間中暗示了這兩種用法:

生成器是用於創建迭代器的簡單而強大的工具。 它們像常規函數一樣編寫,但只要它們想要返回數據就使用 yield 語句。 每次調用 next() 時,生成器都會從它停止的地方繼續(它會記住所有數據值以及最后執行的語句)。

前兩句用生成器函數標識生成器,而第三句用生成器對象標識它們。

盡管存在所有這些混淆,但可以查找Python 語言參考以獲得明確和最終的結論:

yield 表達式僅在定義生成器函數時使用,並且只能在函數定義的主體中使用。 在函數定義中使用 yield 表達式足以使該定義創建生成器函數而不是普通函數。

當調用生成器函數時,它返回一個稱為生成器的迭代器。 然后該生成器控制生成器函數的執行。

因此,在正式和精確的用法中, “生成器”不合格意味着生成器對象,而不是生成器函數。

上述參考是針對 Python 2 的,但Python 3 語言參考說的是同樣的事情。 但是, Python 3 詞匯表指出

generator ... 通常指的是生成器函數,但在某些情況下可能指的是生成器迭代器。 在預期含義不明確的情況下,使用完整的術語可以避免歧義。

每個人都有一個非常好的和詳細的示例答案,我真的很感激。 我只是想為那些在概念上還不太清楚的人提供簡短的幾行答案:

如果您創建自己的迭代器,它會涉及一點點 - 您必須創建一個類並至少實現 iter 和 next 方法。 但是,如果您不想經歷這些麻煩並想快速創建一個迭代器怎么辦。 幸運的是,Python 提供了一種定義迭代器的捷徑。 您需要做的就是定義一個至少有 1 次調用 yield 的函數,現在當您調用該函數時,它會返回“ something ”,它的作用類似於迭代器(您可以調用 next 方法並在 for 循環中使用它)。 這個東西在 Python 中有一個名字叫做 Generator

希望澄清一點。

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)
  • 生成器也用於中斷雙循環

迭代器

滿頁的書是可迭代的,書簽是迭代器

而這個書簽除了移到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

要使用 Generator ...我們需要一個函數

要使用 Iterator ...我們需要nextiter

如前所述:

生成器函數返回一個迭代器對象

迭代器的全部好處:

一次在內存中存儲一​​個元素

以前的答案錯過了這個添加:生成器有一個close方法,而典型的迭代器沒有。 close方法會觸發生成器中的StopIteration異常,該異常可能會在該迭代器的finally子句中被捕獲,以便有機會運行一些清理工作。 這種抽象使其在大型而不是簡單的迭代器中最有用。 一個人可以關閉一個生成器,就像一個人可以關閉一個文件一樣,而不必擔心下面的內容。

也就是說,我個人對第一個問題的回答是:iteratable 只有一個__iter__方法,典型的迭代器只有一個__next__方法,生成器有一個__iter__和一個__next__以及一個額外的close

對於第二個問題,我個人的回答是:在公共接口中,我更傾向於生成器,因為它更具彈性: close方法與yield from具有更大的可組合性。 在本地,我可能會使用迭代器,但前提是它是一個扁平且簡單的結構(迭代器不容易組合)並且有理由相信序列相當短,特別是如果它可能在到達末尾之前停止。 我傾向於將迭代器視為低級原語,除了文字。

對於控制流而言,生成器是一個與 Promise 一樣重要的概念:兩者都是抽象的和可組合的。

無代碼 4 行備忘單:

A generator function is a function with yield in it.

A generator expression is like a list comprehension. It uses "()" vs "[]"

A generator object (often called 'a generator') is returned by both above.

A generator is also a subtype of iterator.

生成器函數、生成器對象、生成器:

Generator 函數就像 Python 中的常規函數​​一樣,但它包含一個或多個yield語句。 生成器函數是一個很好的工具,可以盡可能簡單地創建迭代器對象。 生成器函數返回的迭代器對象也稱為生成器對象生成器

在這個例子中,我創建了一個 Generator 函數,它返回一個 Generator 對象<generator object fib at 0x01342480> 就像其他迭代器一樣,Generator 對象可以在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__方法(​​也稱為迭代器協議),則可以創建自定義迭代器對象。

但是,使用生成器函數創建迭代器要容易得多,因為它們簡化了它們的創建,但是自定義的迭代器給您更多的自由,您還可以根據您的要求實現其他方法,如下例所示。

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

如果沒有其他兩個概念,很難回答這個問題: iterableiterator protocol

  1. iteratoriterable和有什么不一樣? 從概念上講,您在相應的iterator的幫助下iterable迭代。 有一些區別可以幫助在實踐中區分iteratoriterable
    • 一個區別是iterator__next__方法,而iterable沒有。
    • 另一個區別 - 它們都包含__iter__方法。 iterable的情況下,它返回相應的迭代器。 如果是iterator ,它會返回自身。 這有助於在實踐中區分iteratoriterable
>>> x = [1, 2, 3]
>>> dir(x) 
[... __iter__ ...]
>>> x_iter = iter(x)
>>> dir(x_iter)
[... __iter__ ... __next__ ...]
>>> type(x_iter)
list_iterator
  1. python中的iterables對象是什么? liststringrange等。什么是iterators enumeratezipreversed等。我們可以使用上面的方法檢查這一點。 這有點令人困惑。 如果我們只有一種類型,可能會更容易。 rangezip之間有什么區別嗎? 這樣做的原因之一 - range有很多額外的功能 - 我們可以索引它或檢查它是否包含一些數字等(請參閱此處的詳細信息)。

  2. 我們如何自己創建一個iterator 理論上我們可以實現Iterator Protocol (見這里)。 我們需要編寫__next____iter__方法並引發StopIteration異常等等(有關示例和可能的動機,請參見 Alex Martelli 的回答,另請參見此處)。 但實際上我們使用生成器。 到目前為止,這似乎是在python中創建iterators的主要方法。

我可以給你一些更有趣的例子,展示這些概念在實踐中的一些令人困惑的用法:

  • keras我們有tf.keras.preprocessing.image.ImageDataGenerator 這個類沒有__next____iter__方法; 所以它不是迭代器(或生成器);
  • 如果您調用它的flow_from_dataframe()方法,您將獲得具有這些方法的DataFrameIterator 但它沒有實現StopIteration (這在python的內置迭代器中並不常見); 在文檔中,我們可能會讀到“A DataFrameIterator yielding tuples of (x, y) ”——再次混淆了術語的使用;
  • 我們在keras中也有Sequence類,這是生成器功能的自定義實現(常規生成器不適合多線程),但它沒有實現__next____iter__ ,而是對生成器的包裝(它使用yield語句);

您可以比較相同數據的兩種方法:

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

此外,如果您檢查內存占用,生成器占用的內存要少得多,因為它不需要同時將所有值存儲在內存中。

該線程詳細介紹了兩者之間的所有差異,但想在兩者之間的概念差異上添加一些內容:

[...] GoF 書中定義的迭代器從集合中檢索項目,而生成器可以“憑空”產生項目 這就是為什么斐波那契數列生成器是一個常見示例的原因:無限的數字序列不能存儲在集合中。

拉馬略,盧西亞諾。 流利的 Python(第 415 頁)。 奧萊利媒體。 Kindle版。

當然,它並沒有涵蓋所有方面,但我認為它提供了一個很好的概念,什么時候有用。

我正在以一種非常簡單的方式專門為 Python 新手編寫,盡管 Python 在內心深處做了很多事情。

讓我們從最基本的開始:

考慮一個清單,

l = [1,2,3]

讓我們寫一個等價的函數:

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

print(l): [1,2,3] & print(f()) : [1,2,3]

讓列表 l 可迭代:在 python 中,列表始終是可迭代的,這意味着您可以隨時應用迭代器。

讓我們在列表上應用迭代器:

iter_l = iter(l) # iterator applied explicitly

讓我們使函數可迭代,即編寫一個等效的生成器函數。 在 python 中,只要你引入關鍵字yield ; 它成為一個生成器函數,並且迭代器將被隱式應用。

注意:每個生成器在應用隱式迭代器的情況下始終是可迭代的,這里隱式迭代器是關鍵所以生成器函數將是:

def f():
  yield 1 
  yield 2
  yield 3

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

因此,如果您觀察到,一旦您制作了函數 fa 生成器,它就已經是 iter(f)

現在,

l 是列表,在應用迭代器方法“iter”后,它變成了 iter(l)

f 已經是 iter(f),在應用迭代器方法“iter”之后,它變成了 iter(iter(f)),它又是 iter(f)

這有點像您將 int 轉換為 int(x),它已經是 int 並且它將保持 int(x)。

例如 o/p 的:

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

<class 'list_iterator'>

永遠不要忘記這是 Python 而不是 C 或 C++

因此,上述解釋的結論是:

列表 l ~= iter(l)

生成器函數 f == iter(f)

可迭代對象是可以(自然地)迭代的東西。 但是,要做到這一點,您將需要像迭代器對象這樣的東西,而且,是的,術語可能會令人困惑。 可迭代對象包括一個__iter__方法,該方法將返回可迭代對象的迭代器對象。

迭代器對象是實現迭代器協議的對象——一組規則。 在這種情況下,它必須至少有這兩個方法: __iter____next__ __next__方法是一個提供新值的函數。 __iter__方法返回迭代器對象。 在更復雜的對象中,可能有一個單獨的迭代器,但在更簡單的情況下, __iter__返回對象本身(通常return self )。

一個可迭代對象是list對象。 它不是迭代器,但它有一個返回迭代器的__iter__方法。 您可以直接將此方法稱為things.__iter__() ,或使用iter(things)

如果你想遍歷任何集合,你需要使用它的迭代器:

things_iterator = iter(things)
for i in things_iterator:
    print(i)

然而,Python 會自動使用迭代器,這就是為什么你永遠看不到上面的例子。 相反,你寫:

for i in things:
    print(i)

自己編寫迭代器可能很乏味,因此 Python 有一個更簡單的替代方案:生成器函數 生成器函數不是普通函數。 不是運行代碼並返回最終結果,而是延遲代碼,並且函數立即返回生成器對象

生成器對象類似於迭代器對象,因為它實現了迭代器協議。 這對於大多數用途來說已經足夠了。 其他答案中有很多生成器的例子。

簡而言之,迭代器是一個對象,它允許您遍歷另一個對象,無論它是集合還是其他值的來源。 生成器是一個簡化的迭代器,它或多或少地完成相同的工作,但更容易實現。

通常,如果這就是您所需要的,您會選擇發電機。 但是,如果您正在構建一個更復雜的對象,其中包括其他功能中的迭代,您將使用迭代器協議。

所有生成器都是迭代器,但反之則不然。

from typing import Iterator
from typing import Iterable
from typing import Generator

class IT:

    def __init__(self):
        self.n = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.n == 4:
            raise StopIteration
        try:
            return self.n
        finally:
            self.n += 1


def g():
    for i in range(4):
        yield i

def test(it):
    print(f'type(it) = {type(it)}')
    print(f'isinstance(it, Generator) = {isinstance(it, Generator)}')
    print(f'isinstance(it, Iterator) = {isinstance(it, Iterator)}')
    print(f'isinstance(it, Iterable) = {isinstance(it, Iterable)}')
    print(next(it))
    print(next(it))
    print(next(it))
    print(next(it))
    try:
        print(next(it))
    except StopIteration:
        print('boom\n')


print(f'issubclass(Generator, Iterator) = {issubclass(Generator, Iterator)}')
print(f'issubclass(Iterator, Iterable) = {issubclass(Iterator, Iterable)}')
print()
test(IT())
test(g())

Output:

issubclass(Generator, Iterator) = True
issubclass(Iterator, Iterable) = True

type(it) = <class '__main__.IT'>
isinstance(it, Generator) = False
isinstance(it, Iterator) = True
isinstance(it, Iterable) = True
0
1
2
3
boom

type(it) = <class 'generator'>
isinstance(it, Generator) = True
isinstance(it, Iterator) = True
isinstance(it, Iterable) = True
0
1
2
3
boom

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM