1. 程式人生 > >函式進階之生成器和迭代器

函式進階之生成器和迭代器

前提:

列表生成式

給列表a裡的大於5的每一個值加10

a = [1, 2, 5, 6, 7, 8]
a = [i + 10 if i > 5 else i for i in a]  # 可以迴圈任何可迴圈的東西,不過只能寫到列表或元組裡。
print(a)  # [1, 2, 5, 16, 17, 18]

複雜東西列表生成式寫不出來,最多到三元運算了。

 

正文:

生成器是為了省記憶體,不一次釋放,需要一個取一個。

g = (i for i in range(5))
print(g)  # <generator object <genexpr> at 0x101fba048>
print(next(g)) # 0 print(next(g)) # 1 print(next(g)) # 2 print(next(g)) # 3 print(next(g)) # 4 print(next(g)) # 到最後沒有了,會報錯

生成器只能往前走,不能後退。

我們建立了一個generator後,基本上永遠不會呼叫next(),而是通過for迴圈來迭代它,並且不需要關心StopIteration的錯誤,for迴圈會把它自動消化。

g2 = (i for i in range(5))

for i in g2:
    print(i)

generator非常強大。如果推算的演算法比較複雜,用類似列表生成式的for迴圈無法實現的時候,還可以用函式來實現。

 

生成器和range

python2裡range是直接建立列表。
python3裡的range是用生成器做的,是迴圈到了才建立。
python2裡有個xrange,其實就是等於python3裡的range。python3只有range,等於是把python2裡的那個range去掉了。

 

用生成器實現斐波那契

def fib(max):
    n, a, b = 10, 0, 1
    while n < max:
        print('before yield')yield n  # 把函式的執行過程凍結在這一步,並且把b的值,返回給外面的next()
print(b) a, b = b, a + b n += 1 return 'done' print(fib(15)) # <generator object fib at 0x101fba048> 裡面有yield,函式名加括號,內部程式碼根本不執行,只是生成一個生成器物件。 f = fib(15) # return function into a generator next(f) # before yield # first time call next() n = next(f) # 1 # before yield next(f) # 1 # before yield next(f) # 2 # before yield print(n) # 11

yield的作用是把函式裡面的值返回出來到外部

解析:第一次print出 before yield之後,遇到yield,程式終止,再次執行next(f),程式繼續執行print出b的值,然後直到再次走到print('before yield')後,
程式遇到yield n,程式終止,然後再次執行next(f),以此迴圈......

 

生成器的建立方式

1. "列表"生成式(),最多支援三元表示式。 例:g2 = (i for i in range(5))
2. 函式

 

用生成器實現range方法

def range2(n):
    count = 0
    while count < n:
        print('count', count)
        yield count
        count += 1


new_range = range2(10)
r1 = next(new_range)
print(r1)  # 0
r2 = new_range.__next__()  # 和 next(new_range)是一樣的
print(r2)  # 1

 

yield vs return

return 返回並終止函式
yield 返回資料,並凍結當前的執行過程

next 喚醒凍結的函式執行過程,繼續執行,直到遇到下一個yield

 

函式有了yield之後
1.函式名加()就變成了一個生成器
2.return在生成器裡,代表生成器的終止,直接報錯

 

生成器和檔案操作

with open('test.txt') as f:
    for line in f:
        pass

其實在for迴圈這個檔案的時候,就是在迴圈一個生成器。

 

生成器send方法

def range2(n):
    count = 0
    while count < n:
        print('count', count)
        sign = yield count
        count += 1
        if sign == 'stop':
            print('----sign ', sign)
            break


new_range = range2(10)
n1 = next(new_range)  # count 0

n2 = new_range.__next__()  # count 1

n3 = new_range.send(None)  # count 2 這條語句等於next,因為next(或__next__)方法就是預設往生成器裡面傳送了一個None

n4 = new_range.send('stop')  # 終止,程式報錯,列印----sign  stop

 

send的作用:
1.喚醒並繼續執行
2. 傳送一個資訊到生成器的內部

next()預設往生成器裡傳送一個None

 

迭代器

可以被next()函式呼叫並不斷返回下一個值的物件稱為迭代器:Iterator。

可以使用isinstance()判斷一個物件是否是Iterable物件

from collections import Iterable

print(isinstance(123, Iterable))  # False
print(isinstance('abc', Iterable))  # True

只有生成器是迭代器。

from collections import Iterator

li = [i for i in range(10)]
print(isinstance(li, Iterator))  # False

但是可以把列表、字串等變成迭代器

li = iter(li)

print(li.__next__())  # 0
print(li.__next__())  # 1

Python的Iterator物件表示的是一個數據流,Iterator物件可以被next()函式呼叫並不斷返回下一個資料,直到沒有資料時丟擲StopIteration錯誤。
可以把這個資料流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函式實現按需計算下一個資料,所以Iterator的計算是惰性的,
只有在需要返回下一個資料時它才會計算。Iterator甚至可以表示一個無限大的資料流,例如全體自然數。而使用list是永遠不可能儲存全體自然數的。

 

需要注意的是:

  1.列表用len能知道長度,但是迭代器不能知道
  2.迭代器比生成器的範圍要大一些,學了面向物件後不用生成器也能next。

 

凡是可作用於for迴圈的物件都是Iterable型別;
凡是可作用於next()函式的物件都是Iterator型別,它們表示一個惰性計算的序列;
集合資料型別如list、dict、str等是Iterable但不是Iterator,不過可以通過iter()函式獲得一個Iterator物件。
Python3的for迴圈本質上就是通過不斷呼叫next()函式實現的

 

生成器算得上是Python語言中最吸引人的特性之一,生成器其實是一種特殊的迭代器,不過這種迭代器更加優雅。
它不需要再像上面的類一樣寫__iter__()和__next__()方法了,只需要一個yiled關鍵字。
生成器一定是迭代器(反之不成立),因此任何生成器也是以一種懶載入的模式生成值。