Python 生成器和迭代器,yield語句


什么是迭代器

顧名思義,迭代器就是用於迭代操作(for 循環)的對象,它像列表一樣可以迭代獲取其中的每一個元素,任何實現了 __next__ 方法 (python2 是 next)的對象都可以稱為迭代器。

它與列表的區別在於,構建迭代器的時候,不像列表把所有元素一次性加載到內存,而是以一種延遲計算(lazy evaluation)方式返回元素,這正是它的優點。比如列表含有中一千萬個整數,需要占超過400M的內存,而迭代器只需要幾十個字節的空間。因為它並沒有把所有元素裝載到內存中,而是等到調用 next 方法時候才返回該元素(按需調用 call by need 的方式,本質上 for 循環就是不斷地調用迭代器的next方法)。

以斐波那契數列為例來實現一個迭代器:

class Fib:
    def __init__(self, n):
        self.prev = 0
        self.cur = 1
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        if self.n > 0:
            value = self.cur
            self.cur = self.cur + self.prev
            self.prev = value
            self.n -= 1
            return value
        else:
            raise StopIteration()
    # 兼容python2
    def __next__(self):
        return self.next()

f = Fib(10)
print([i for i in f])
#[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

什么是生成器

知道迭代器之后,就可以正式進入生成器的話題了。普通函數用 return 返回一個值,和 Java 等其他語言是一樣的,然而在 Python 中還有一種函數,用關鍵字 yield 來返回值,這種函數叫生成器函數,函數被調用時會返回一個生成器對象,生成器本質上還是一個迭代器,也是用在迭代操作中,因此它有和迭代器一樣的特性,唯一的區別在於實現方式上不一樣,后者更加簡潔

最簡單的生成器函數:

>>> def func(n):
...     yield n*2
...
>>> func
<function func at 0x00000000029F6EB8>
>>> g = func(5)
>>> g
<generator object func at 0x0000000002908630>
>>>

func 就是一個生成器函數,調用該函數時返回對象就是生成器 g ,這個生成器對象的行為和迭代器是非常相似的,可以用在 for 循環等場景中。注意 yield 對應的值在函數被調用時不會立刻返回,而是調用next方法時(本質上 for 循環也是調用 next 方法)才返回

>>> g = func(5)
>>> next(g)
10

>>> g = func(5)
>>> for i in g:
...     print(i)
...
10

那為什么要用生成器呢?顯然,用生成器在逼格上要比迭代器高幾個等級,它沒有那么多冗長代碼了,而且性能上一樣的高效,為什么不用呢?來看看用生成器實現斐波那契數列有多簡單。

def fib(n):
    prev, curr = 0, 1
    while n > 0:
        n -= 1
        yield curr
        prev, curr = curr, curr + prev

print([i for i in fib(10)])
#[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

生成器表達式

器表達式與列表推導式長的非常像,但是它倆返回的對象不一樣,前者返回生成器對象,后者返回列表對象。

>>> g = (x*2 for x in range(10))
>>> type(g)
<type 'generator'>
>>> l = [x*2 for x in range(10)]
>>> type(l)
<type 'list'>

前面已經介紹過生成器的優勢,就是迭代海量數據時,顯然生成器更合適。

 

Python中生成器和迭代器的區別(代碼在Python3.5下測試):
Num01–>迭代器
定義:
對於list、string、tuple、dict等這些容器對象,使用for循環遍歷是很方便的。在后台for語句對容器對象調用iter()函數。iter()是python內置函數。
iter()函數會返回一個定義了next()方法的迭代器對象,它在容器中逐個訪問容器內的元素。next()也是python內置函數。在沒有后續元素時,next()會拋出一個StopIteration異常,通知for語句循環結束。

迭代器是用來幫助我們記錄每次迭代訪問到的位置,當我們對迭代器使用next()函數的時候,迭代器會向我們返回它所記錄位置的下一個位置的數據。實際上,在使用next()函數的時候,調用的就是迭代器對象的_next_方法(Python3中是對象的_next_方法,Python2中是對象的next()方法)。所以,我們要想構造一個迭代器,就要實現它的_next_方法。但這還不夠,python要求迭代器本身也是可迭代的,所以我們還要為迭代器實現_iter_方法,而_iter_方法要返回一個迭代器,迭代器自身正是一個迭代器,所以迭代器的_iter_方法返回自身self即可。

一些術語的解釋:
1,迭代器協議:對象需要提供next()方法,它要么返回迭代中的下一項,要么就引起一個StopIteration異常,以終止迭代。

2,可迭代對象:實現了迭代器協議對象。list、tuple、dict都是Iterable(可迭代對象),但不是Iterator(迭代器對象)。但可以使用內建函數iter(),把這些都變成Iterable(可迭代器對象)。

3,for item in Iterable 循環的本質就是先通過iter()函數獲取可迭代對象Iterable的迭代器,然后對獲取到的迭代器不斷調用next()方法來獲取下一個值並將其賦值給item,當遇到StopIteration的異常后循環結束

Python自帶容器對象案例:
# 隨便定義一個list
listArray=[1,2,3]
# 使用iter()函數
iterName=iter(listArray)
print(iterName)
# 結果如下:是一個列表list的迭代器
# <list_iterator object at 0x0000017B0D984278>

print(next(iterName))
print(next(iterName))
print(next(iterName))
print(next(iterName))#沒有迭代到下一個元素,直接拋出異常
# 1
# 2
# 3
# Traceback (most recent call last):
# File "Test07.py", line 32, in <module>
# StopIteration

Python中一個實現了_iter_方法和_next_方法的類對象,就是迭代器,如下案例是計算菲波那切數列的案例

class Fib(object):
def __init__(self, max):
super(Fib, self).__init__()
self.max = max

def __iter__(self):
self.a = 0
self.b = 1
return self

def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib

# 定義一個main函數,循環遍歷每一個菲波那切數
def main():
# 20以內的數
fib = Fib(20)
for i in fib:
print(i)

# 測試
if __name__ == '__main__':
main()
解釋說明:

在本類的實現中,定義了一個_iter_(self)方法,這個方法是在for循環遍歷時被iter()調用,返回一個迭代器。因為在遍歷的時候,是直接調用的python內置函數iter(),由iter()通過調用_iter_(self)獲得對象的迭代器。有了迭代器,就可以逐個遍歷元素了。而逐個遍歷的時候,也是使用內置的next()函數通過調用對象的_next_(self)方法對迭代器對象進行遍歷。所以要實現_iter_(self)和_next_(self)這兩個方法。

而且因為實現了_next_(self)方法,所以在實現_iter_(self)的時候,直接返回self就可以。

總結一句話就是:
在循環遍歷自定義容器對象時,會使用python內置函數iter()調用遍歷對象的_iter_(self)獲得一個迭代器,之后再循環對這個迭代器使用next()調用迭代器對象的_next_(self)。

注意點:_iter_(self)只會被調用一次,而_next_(self)會被調用 n 次,直到出現StopIteration異常。

Num02–>生成器
作用:
>延遲操作。也就是在需要的時候才產生結果,不是立即產生結果。

注意事項:
>生成器是只能遍歷一次的。
>生成器是一類特殊的迭代器。

分類:
第一類:生成器函數:還是使用 def 定義函數,但是,使用yield而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次從它離開的地方繼續執行。

如下案例加以說明:

# 菲波那切數列
def Fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return '親!沒有數據了...'
# 調用方法,生成出10個數來
f=Fib(10)
# 使用一個循環捕獲最后return 返回的值,保存在異常StopIteration的value中
while True:
try:
x=next(f)
print("f:",x)
except StopIteration as e:
print("生成器最后的返回值是:",e.value)
break

第二類:生成器表達式:類似於列表推導,只不過是把一對大括號[]變換為一對小括號()。但是,生成器表達式是按需產生一個生成器結果對象,要想拿到每一個元素,就需要循環遍歷。

如下案例加以說明:

# 一個列表
xiaoke=[2,3,4,5]
# 生成器generator,類似於list,但是是把[]改為()
gen=(a for a in xiaoke)
for i in gen:
print(i)
#結果是:

# 為什么要使用生成器?因為效率。
# 使用生成器表達式取代列表推導式可以同時節省 cpu 和 內存(RAM)。
# 如果你構造一個列表(list)的目的僅僅是傳遞給別的函數,
# 比如 傳遞給tuple()或者set(), 那就用生成器表達式替代吧!

# 本案例是直接把列表轉化為元組
kk=tuple(a for a in xiaoke)
print(kk)
#結果是:
(2, 3, 4, 5)

# python內置的一些函數,可以識別這是生成器表達式,外面有一對小括號,就是生成器
result1=sum(a for a in range(3))
print(result1)
# 列表推導式
result2=sum([a for a in range(3)])
print(result2)
————————————————
版權聲明:本文為CSDN博主「ITxiaoke」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u014745194/article/details/70176117

 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM