python---基礎知識回顧(七)迭代器和生成器

前戲:迭代器和生成器

迭代:數據結構

若是給定一個list或tuple,咱們能夠經過for循環來遍歷這個list或tuple,這種遍歷咱們稱爲迭代(Iteration)。
Python的for循環不只能夠用在list或tuple上,還能夠做用在其餘可迭代對象上(用isinstance判斷)

能夠直接做用於for循環的對象統稱爲可迭代對象ide

能夠直接做用於for循環的數據類型有如下幾種:
一類是集合數據類型,如list、tuple、dict、set、str等;(稱爲容器<容器是一種把多個元素組織在一塊兒的數據結構>,不少容器都是可迭代的) 一類是generator,包括生成器和帶yield的generator function。 這些能夠直接做用於for循環的對象統稱爲可迭代對象:Iterable。

(一)迭代器

一個實現了__iter__方法的對象是可迭代的,一個實現了__next__方法的對象則是迭代器

對於序列和字典的可迭代,是由於在該對象中實現了上面的兩個方法函數

__iter__方法會返回一個迭代器,而所謂的迭代器就是具備__next__方法的對象。在調用__next__方法時,迭代器會返回他的下一個值。如果next方法被調用牡丹石迭代器中沒有值能夠返回,就會引起一個StopIteration異常spa

迭代器的優勢:須要數據就去獲取,而不是一次獲取所有數據code

 

相對於咱們一次性取出數據,放在列表等類型中,若數據量過大,那麼列表會佔據大量的內存。並且對於這些數據,咱們如果只使用一次就釋放的話,那麼放在列表中實在是太過浪費內存。
更好的方法就是使用迭代器。迭代器只會取出當前須要的數據方法內存中。

例如Django中的queryset惰性機制中就有說起迭代器的好處(在處理大量的數據時)對象

栗子:不使用列表的案例,由於若是使用列表,那麼列表長度將會是無窮大。佔據空間將會是巨大的。blog

斐波那契數列:內存

class Fibs:
    def __init__(self):
        self.a = 0
        self.b = 1

    def __next__(self):
        self.a,self.b = self.b,self.a+self.b
        return self.a

    def __iter__(self):
        return self

f = Fibs()

for i in f:
    if i > 1000:
        print(i)    #1597
        break

補充:內建函數iter能夠從可迭代的對象中獲取迭代器ci

>>> a = [1,2,3,]
>>> b = iter(a)
>>> type(b)
<class 'list_iterator'>
>>> next(b)
1
>>> next(b)
2
>>> next(b)
3
>>> next(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

能夠知道迭代器是一次性消耗品(只會向前獲取,不會向後獲取),當耗盡時就會觸發StopIteration異常
如果想保留一份數據,能夠用deepcopy

從迭代器中獲取序列:作用域

class Fibs:
    def __init__(self):
        self.a = 0
        self.b = 1

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

    def __iter__(self):
        return self

f = Fibs()

ls = list(f)
print(ls) #[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]

使用list構造方法顯示的將迭代器轉換爲列表

class list(object):
        def __init__(self, seq=()): # known special case of list.__init__
        """
        list() -> new empty list
        list(iterable) -> new list initialized from iterable's items
     (如果迭代器,那麼新的列表則是迭代器的全部成員,結束是以StopIteration爲標誌,如果上面沒有觸發,那麼會一直去擴展列表)
# (copied from class doc) """ pass

 結束

 


應該還記得列表推導式(生成式)<順道回憶下lambda表達式>

>>> [x for x in range(100) if x % 7 == 0]
[0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]

經過列表推導式,咱們能夠直接生成一個列表,一樣的,這個列表的內存也是受到限制的,當咱們使用列表推導式,一次生成一個超大數量的列表,會佔據大量內存,然而,若咱們只是訪問了前面幾個,那麼後面的空間佔用幾乎是無用的。

for i in [x for x in range(100) if x % 7 == 0]:
    if i < 50:
        print(i)
    else:
        break

在上面案例中,咱們只是想去獲取知足條件的數據的一部分。可是在進行循環時,並不會馬上進行,而是須要將列表生成式所有執行後,才容許去進行循環。而咱們所須要的數據僅僅是列表中的前一部分,可是列表推導式一次性將數據所有生成。佔據大量無用的空間。那麼咱們是否能夠作到像迭代器那樣,須要的時候再去獲取。從而避免數據冗餘

(二)生成器

生成器都是迭代器。生成器是一種用普通函數語法定義的迭代器

建立一個生成器方法有多種:

其中第一種與列表推導式十分類似,只是須要將中括號[]變爲小括號()

>>> b = [x for x in range(100) if x % 7 == 0]
>>> type(b)
<class 'list'>
[0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]

>>> b = (x for x in range(100) if x % 7 == 0)
>>> type(b)
<class 'generator'>
>>> next(b)
0
>>> next(b)
7
>>> next(b)
14
>>> for i in b:
... print(i)

當數據所有取出後也會觸發StopIteration錯誤

 

另一種是:任何包括yield語句的函數均可以稱爲生成器。

這裏一樣以斐波那契數列爲例:

def fibs(max):
    n,a,b = 0,0,1
    while n < max:
        yield a  #yield語句
        n += 1
        a, b = b, a+b


f = fibs(8)

for i in f:
    print(i)    #0 1 1 2 3 5 8 13

生成器和普通函數的行爲有很大的區別。

  1. 不像return返回一次結果就結束函數,而是能夠返回屢次結果。從什麼for循環能夠看出,這一個函數返回了不止一次結果
  2. 每產生一個值(即在yield語句中返回的值),函數就會被凍結,不在執行:即函數停在那點等待被從新喚醒。被從新喚醒後就從以前通知的那點開始執行
def 函數:
    ...
    yield  1    執行第一次後返回值1後凍結,不在執行,等待第二次
    ...    #執行第二次時會向下繼續執行,直到下一次yield
    ...
    ...
    yield  2    #第二次凍結(這個過程包括了執行上面的邏輯語句)
    ...
    ...
    ...
    yield  3

案例:

>>> def flatten(nested):
...     for sublist in nested:
...             for ele in sublist:
...                     yield ele

>>> for num in flatten(nested):
...     print(num)
...
1
2
3
4
5
>>>

也能夠同上面迭代器同樣使用list顯示轉換爲列表。

>>> list(flatten(nested))
[1, 2, 3, 4, 5]
>>>

可是這樣會馬上實例化列表,喪失了迭代的優點。

def nrange(num):
    temp = -1
    while True:
        temp = temp + 1
        if temp >= num:
            return
        else:
            yield temp

for i  in nrange(10):
    print(i)
含有return的自定義nrange生成器

通用生成器:

生成器是一個包含yield關鍵字的函數。當他被調用的時候,在函數體中的代碼不會執行,而是會返回一個迭代器。每次請求一個值,就會執行生成器中的代碼,直到遇到一個yield或者return語句。(yield意味着生成一個值,並凍結執行,等待下一次執行。return意味着生成器要通知執行)

生成器由兩部分組成:生成器的函數和生成器的迭代器。生成器的函數使用def語句定義的,包含yield的。生成器的迭代器是這個函數的返回部分。合在一塊兒就是生成器。

生成器方法:

生成器中新特徵:能夠爲生成器提供值,而不是隻像上面那樣生成器爲外面返回值。生成器內外能夠進行交流

外部做用域訪問生成器的send方法,能夠向生成器內部傳遞消息(任意對象),此時yield再也不是一個返回值語句,而是一個表達式

send方法和yield語句的執行區別:

yield

def rep2(val):
yield val print("aaa") val += 10 print("bbb") yield val r = rep2(33) print(next(r))  #能夠看出,執行yield返回值後,就凍結在該條語句,再也不向下執行,只有當下一個next出現,纔會繼續執行
def rep(val):new = (yield val)  #接收send發送過來的數據
        if new:
            print(new)
        new = (yield val)
        if new:
            print(new)

f = rep(33)
v = next(f)

f.send("dsad")  #會打印出來dasd  能夠看出,當send發送數據後,在接收數據後,會繼續向下執行,直到下一個yield表達式出現

 

send方法使用:

注意:在使用send方法時,只有當生成器掛起之後纔有意義(也就是說:在yield函數第一次執行以後)

def rep(val):
        new = (yield val)
        if new:
            print(new)
        new = (yield val)
        if new:
            print(new)

開始執行:

若沒有將生成器掛起:

TypeError: can't send non-None value to a just-started generator

因此,咱們在使用時須要先掛起生成器。掛起方法有兩種:

第一種:

r = rep(33)
v = next(r)  #這裏正常執行next獲取yield返回,後面就能夠正常使用send
print(v)

r.send("dsad")
r.send("dsadds")

第二種(由剛剛的TypeError能夠知道不能send一個非None值,在第一次時,因此咱們能夠直接在第一次時send(None)):

r.send(None)
r.send("dsad")
r.send("dsadds")

兩種方法,強烈推薦第二種

緣由:使用send方法時,須要注意兩點

1.須要先將生成器掛起,此時纔有意義

2.send方法第一次使用時,也是須要進行一次next()方法執行,或者send(None)執行

第2條件是在咱們使用send前有其餘yield語句返回時,能夠了解到

def rep(val):
        yield val
        new = (yield val)
        if new:
            print(new)
        new = (yield val)
        if new:
            print(new)

r = rep(33)
v = next(r)  #掛起生成器
print(v)
v = next(r)  #激活send方法
print(v)
r.send("dsad")
r.send("dsadds")
---------------- 正常輸出 #
33 #33 #dsad #dsadds
如果只是掛起了生成器,沒有激活send方法,那麼默認第一個send方法會拿去激活
r = rep(33) v = next(r) print(v) # v = next(r)  #沒有去激活send方法 # print(v) r.send("dsad")  #第一個send方法會被用到去激活send r.send("dsadds")    #這個纔是正常的信息傳入
----------

 #33
 #dsadds

因此咱們最好使用send(None)表示去激活send方法,不易混淆

def rep(val):
        yield val
        new = (yield val)
        if new:
            print(new)
        new = (yield val)
        if new:
            print(new)



r = rep(33)

v = next(r)    #掛起生成器         也能夠用send(None)去掛起生成器,可是仍是不要這樣作,兩個套用容易混淆
print(v)

r.send(None)    #激活send方法(在首次使用send時使用)

r.send("dsad")
r.send("dsadds")
--------------

 #33
 #dsad
 #dsadds

另外補充下send方法會獲取到yield表達式中的返回值

def rep(val):
        yield val
        new = (yield val)
        if new:
            print(new)
        new = (yield val)
        if new:
            print(new)



r = rep(33)
v = next(r)
print(v)

v = r.send(None)
print(v,1)
v = r.send("dsad")
print(v,2)
v = r.send("dsadds")  #在最後一個send方法時,沒有返回值
print(v,3)
-----------------------
33
33 1
dsad
33 2
dsadds

send方法會依次獲取yield表達式的返回值,因此在第三個send方法使用時,並無yield與之對應,因此沒有值。

具體緣由暫不討論。

注意區分yield語句和yield表達式

生成器的定義是:包含yield語句的函數是生成器

 

再進一步討論:send方法

 1 def rep(val):
 2         yield val  #這裏已經含有yield語句,此函數是生成器,咱們在下面執行的next()只是正常執行這條語句
 3         print("t1")
 4         new = (yield val)
 5         print("t2")
 6         if new:
 7             print("t3")
 8             print(new)
 9         new = (yield val)
10         if new:
11             print(new)
12 r = rep(33)

 

(1):正常執行yield語句(行2),返回值,而且凍結到行2,不在向下執行

v = next(r)
print(v)
-------
#33

那麼如何執行到下面的yield表達式

(2)這時就須要一條語句,去聯繫yield語句和yield表達式

v = next(r)
print(v)

v = r.send(None)  
print(v,1)
---------------------
33

t1 33 1
由上面的兩個結果能夠看出send(None)執行的語句是
yield val
print("t1")
new = (yield val)
這兩條描紅語句,由上面的yield語句,執行到下面yield表達式中的返回值語句中,因此send(None)的返回值就是這裏的yield返回的值

(3)下面的send方法執行一致

v = next(r)
print(v)

v = r.send(None)
print(v,1)
v = r.send("dsad")
print(v,2)

----------------------
33
t1
33 1

由上面(2)的yield表達式開始(原來只是執行到返回值),如今開始賦值(send("dasd"))傳遞進去,而後執行到下一條yield返回值語句 t2 t3 dsad
------------------------
new = (yield val)  從這裏賦值開始,到下面返回值結束
print("t2")
if new:
print("t3")
print(new)
new = (yield val)

 (4)最後一步執行

v = next(r)
print(v)

v = r.send(None)
print(v,1)
v = r.send("dsad")
print(v,2)
v = r.send("dsadds")
print(v,3)

--------------------------
33
t1
33 1
t2
t3
dsad
33 2
dsadds
--------------------------
new = (yield val)
if new:
print(new)
這裏語句中不在含有返回值,因此咱們最後沒法接收到值

當前面沒有yield語句時,執行也是類似的

def rep(val):
        # yield val
        print("t1")      #先執行print('t1')和yield val
        new = (yield val)   #而後執行賦值和向下執行到下一個yield 返回值語句
        print("t2")
        if new:
            print("t3")
            print(new)
        new = (yield val)  #執行賦值,而且將下面語句執行完成*無返回值
        if new:
            print(new)

-----------------------

r = rep(33)

v = r.send(None)
print(v,1)
v = r.send("dsad")
print(v,2)
v = r.send("dsadds")
print(v,3)    #因爲無返回值,不會去執行
-----------------------
t1 33 1 t2 t3 dsad 33 2 dsadds

能夠看爲:

若前面沒有yield語句,則須要進行生成器掛起。(使用next或者send(None)),而後再使用send執行,方法類似

如有yield語句,就已是生成器,咱們只須要正常執行他(使用next或者send(None)),而後須要再次使用去激活send方法(鏈接yield語句和yield表達式中的返回值語句).....

 

案例:生成器實現文件流

#文件輸入流
def FileInputStream(filename):
    try:
        f = open(filename,"r")
        for line in f:
            for byte in line:  #按字節獲取數據
                yield byte
    except Exception as e:
        print(repr(e))  #正常讀取文件無錯誤
    finally:
        f.close()
        return

#文件輸出流 def FileOutputStream(inputStream,filename):
try: f = open(filename,"w") while True: byte = next(inputStream) #如果在調用next方法時,迭代器沒有值能夠返回,就會引起一個StopIteration錯誤 f.write(byte) except StopIteration as e: print(repr(e)) #StopIteration() f.close() return FileOutputStream(FileInputStream('f'),"t2")
相關文章
相關標籤/搜索