[英]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 ...我們需要next
和iter
如前所述:
生成器函數返回一個迭代器對象
迭代器的全部好處:
一次在內存中存儲一個元素
以前的答案錯過了這個添加:生成器有一個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
如果沒有其他兩個概念,很難回答這個問題: iterable
和iterator protocol
。
iterator
和iterable
和有什么不一樣? 從概念上講,您在相應的iterator
的幫助下iterable
迭代。 有一些區別可以幫助在實踐中區分iterator
和iterable
:
iterator
有__next__
方法,而iterable
沒有。__iter__
方法。 在iterable
的情況下,它返回相應的迭代器。 如果是iterator
,它會返回自身。 這有助於在實踐中區分iterator
和iterable
。>>> x = [1, 2, 3]
>>> dir(x)
[... __iter__ ...]
>>> x_iter = iter(x)
>>> dir(x_iter)
[... __iter__ ... __next__ ...]
>>> type(x_iter)
list_iterator
python
中的iterables
對象是什么? list
、 string
、 range
等。什么是iterators
? enumerate
、 zip
、 reversed
等。我們可以使用上面的方法檢查這一點。 這有點令人困惑。 如果我們只有一種類型,可能會更容易。 range
和zip
之間有什么區別嗎? 這樣做的原因之一 - range
有很多額外的功能 - 我們可以索引它或檢查它是否包含一些數字等(請參閱此處的詳細信息)。
我們如何自己創建一個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.