1. 程式人生 > >Python生成器和叠代器

Python生成器和叠代器

fib 函數 ext utf 組成 exc 功能 方法 closed

一、列表生成式

  如果有這樣一個列表[1,2,3,4,5,6,7,8,9,10] ,現在需要將列表裏的每個元素乘以2,應該怎麽做呢?

  方法一:  

# -*-coding:utf-8-*-

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for index,i in enumerate(a):
    a[index] *= 2
print(a)   # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

  方法二:

#-*- coding:utf-8 -*-

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print
(list(map(lambda x:x*2,a))) # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

  似乎已經可以實現了,但是還有沒有其他方法呢?

  答案當然是有的,而且還很簡潔,功能更強,不信,接著看:

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

#列表生成式
a = [1,2,3,4,5,6,7,8]
a = [i if i< 4 else i*i for i in a] # 前三個數不變,後面的數都平方
print(a) #輸出:[1, 2, 3, 16, 25, 36, 49, 64]

  註意:    

    [i if i< 4 else i*i for i in a]可視為兩部分,i if i<4 else i*i 為三元運算,視為一個整體,
  看成一個’變量‘,for i in a 和這個變量組成列表生成式,i每在列表a中被賦值一次,‘變量‘就運行一次。
  變量得出的結果就是列表中的元素。
   這種寫法就是列表生成式。

二、生成器
  1、生成器的由來:
    通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,
  不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素占用的空間都白白浪費了。
    所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出後續的元素呢?
  這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。
  2、創建生成器的簡單方法
L = [x*2 for x in range(10)]
print(l)   # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 列表

g = (x * 2  for x in range(10))
print(g)  # <generator object <genexpr> at 0x1022ef630>  生成器

#創建L和g的區別僅在於最外層的[]和(),L是一個list,而g是一個generator。

    那我們怎樣取出我們想要的值呢?

    方法一:通過next()函數獲得generator的下一個返回值。

g = (x * 2  for x in range(4))
print(g)  # <generator object <genexpr> at 0x1022ef630>  生成器
print(next(g)) # 0
print(next(g)) # 2
print(next(g)) # 4
print(next(g)) # 6
print(next(g)) # StopIteration  生成結束會報錯

    註意:generator保存的是算法,每次調用next(g)就計算出g的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration的錯誤。

    方法二(推薦):使用for循環,因為generator也是可叠代對象,至於可叠代對象待會兒會說,現在你知道生成器可使用for循環就行了。 

a = (i for i in range(3))
for i in a: # for循環輸出生成器完成後不會報錯,但while和手動生成完後會報錯
    print(i)
‘‘‘
輸出:
    0
    1
    2
‘‘‘

  3、我們可以推測一下生成器有哪些特點:

    主要是這樣三個:取一次生成一個,只能向前,生成完成後報錯誤提示(StopIteration)。

  4、生成器的實際運用

    先看一個斐波那契數列是怎樣用函數生成的。 

def fib(max):
    n,a,b = 0,0,1  #  設置初始值
    while n < max:  # 執行次數
        print(b)         # 打印每次執行後的值
        a,b = b,a+b   # 交換賦值
        n += 1

    return end     # 退出

fib(6)  # 函數調用

  那這和生成器有什麽關系呢?其實原理是一樣的。fib函數實際上是定義了斐波拉契數列的推算規則,可以從第一個元素開始,推算出後續任意的元素,這種邏輯其實非常類似generator。

  我們來稍微修改一下這個函數:

# 斐波拉契數列  和 yield
def fib(max):
    n,a,b = 0,0,1
    while n < max:
        #print(‘before yield!‘)
        yield b  # 把函數的執行過程凍結在這一步,並且把b的值,返回給外面的next()
        #print(b)
        a,b = b,a+b
        n += 1
    return Done
f = fib(6) # 內部代碼不執行,只是將函數轉為生成器
#循環輸出
# for i in f:
#     print(i)

print(next(f))# 輸出:1    # next()喚醒凍結的執行過程,繼續執行,直到碰到下一個yield
print(next(f),type(f))  # 輸出:1  <class ‘generator‘>
print(next(f))  # 輸出:2
print(f.__next__()) # 輸出:3 #f.__next__()等同於next(f)


  這就是定義generator的另一種方法。如果一個函數定義中包含yield關鍵字,那麽這個函數就不再是一個普通函數,而是一個generator。

  函數和生成器執行流程的區別:函數是順序執行,遇到return語句或者最後一行函數語句就返回。而變成generator的函數,
在每次調用next()的時候執行,遇到yield語句返回,再次被next()調用時從上次返回的yield語句處繼續執行。 
def func(n):
    count = 0
    while count < n:
        print(count,count)
        sign = yield count # yield 把函數的執行過程凍結在這一步,並且把count的值,返回給外面的next()
        print(--sign--,sign)
        count += 1
    return 666

f = func(3)
print( next(f)) # 0
print( next(f)) # 1
print(f.send(pause))  # 發送消息,喚醒並繼續執行
print( next(f)) # 2 print( next(f)) # 報錯:StopIteration: 666 

‘‘‘
函數中有了yield之後
   1、函數名加()就變成了一個生成器
2、return在生成器裏,代表生成器的中止,直接報錯

next 喚醒並繼續執行,只能默認發送消息None

函數中有了send之後,send(‘pause‘)
   1、喚醒並繼續執行
   2、發送一個消息到生成器內部

‘‘‘
  我們在循環過程中不斷調用yield,就會不斷中斷。當然要給循環設置一個條件來退出循環,不然就會產生一個無限數列出來。
同樣的,把函數改成generator後,我們基本上從來不會用next()來獲取下一個返回值,而是直接使用for循環來叠代: 
for n in fib(6):
    print(n)
‘‘‘
1
1
2
3
5
8
‘‘‘

  但是用for循環調用generator時,發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:

技術分享圖片
 g = fib(6)
 while True:
    try:
        x = next(g)
        print(g:, x)
    except StopIteration as e:
         print(Generator return value:, e.value)
         break
‘‘‘
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
‘‘‘
View Code

  還可通過yield實現在單線程的情況下實現並發運算的效果。

技術分享圖片
#_*_coding:utf-8_*_

import time
def consumer(name):
    print("%s 準備吃包子啦!" %name)
    while True:
       baozi = yield

       print("包子[%s]來了,被[%s]吃了!" %(baozi,name))


def producer(name):
    c = consumer(A)
    c2 = consumer(B)
    c.__next__()
    c2.__next__()
    print("開始準備做包子啦!")
    for i in range(10):
        time.sleep(1)
        print("做了2個包子!")
        c.send(i)
        c2.send(i)

producer("jack")
View Code

  

三、叠代器

  1、定義:可以被next()函數調用並不斷返回下一個值的對象稱為叠代器,Iterator。

  2、可直接作用於for循環的數據類型有以下幾種:     

    一類是:集合數據類型,如list,tuple,dict,set,str等
    一類是:generator,包括生成器和帶yield的生成器函數
    這樣可直接作用於for 循環的對象統稱為可叠代對象:Iterable
    可以使用 isinstance()判斷一個對象是否是Iterable對象

  3、可以使用 isinstance()判斷一個對象是否是Iterable對象
from collections import Iterable
print(isinstance([],Iterable)) # True
print(isinstance(abc,Iterable)) # True

  4、可以使用 isinstance()判斷一個對象是否是Iterator(叠代器)

from collections import Iterator
print(isinstance((x for x in range(5)),Iterator)) # True
print(isinstance([],Iterator)) # False

  5、可叠代對象和叠代器的區別:

      list、dict、str等雖然都是Itrable,但並不是叠代器,可以通過 iter()函數把list,dict,str等變成叠代器。  

from collections import Iterator
print(isinstance(iter([]),Iterator)) # True
print(isinstance(iter(abc),Iterator)) # True

  6、叠代器和生成器的區別:

      叠代器包括生成器,生成器都是叠代器(Iterator),但叠代器不一定是生成器。

  7、小結: 

    凡是可作用於for循環的對象都是可叠代對象(Iterable).
    凡是可作用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列。
    集合數據類型如list,dict,str等都是可叠代對象但不是叠代器,但可以通過iter()函數獲得一個叠代器對象。
    python3中 for循環本質上就是通過不斷調用next()函數實現的。

 

  

Python生成器和叠代器