迭代器和生成器

一 迭代和可迭代協議

什麼叫迭代編程

1234不能夠for循環,是由於它不可迭代。那麼若是「可迭代」,就應該能夠被for循環了。ide

這個咱們知道呀,字符串、列表、元組、字典、集合均可以被for循環,說明他們都是可迭代的函數

咱們怎麼來證實這一點呢?測試

from collections import Iterable
                             
l = [1,2,3,4]                
t = (1,2,3,4)                
d = {1:2,3:4}                
s = {1,2,3,4}                
                             
print(isinstance(l,Iterable))
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(s,Iterable))

結合咱們使用for循環取值的現象,再從字面上理解一下,其實迭代就是咱們剛剛說的,能夠將某個數據集內的數據「一個挨着一個的取出來」,就叫作迭代大數據

 

可迭代協議spa

能夠被迭代要知足的要求就叫作可迭代協議可迭代協議的定義很是簡單,就是內部實現了__iter__方法。3d

 

迭代器協議code

'''
dir([1,2].__iter__())是列表迭代器中實現的全部方法,dir([1,2])是列表中實現的全部方法,都是以列表的形式返回給咱們的,爲了看的更清楚,咱們分別把他們轉換成集合,
而後取差集。
'''
#print(dir([1,2].__iter__()))
#print(dir([1,2]))
print(set(dir([1,2].__iter__()))-set(dir([1,2])))

結果:
{'__length_hint__', '__next__', '__setstate__'}

咱們看到在列表迭代器中多了三個方法,那麼這三個方法都分別作了什麼事呢?協程

iter_l = [1,2,3,4,5,6].__iter__()
#獲取迭代器中元素的長度
print(iter_l.__length_hint__())
#根據索引值指定從哪裏開始迭代
print('*',iter_l.__setstate__(4))
#一個一個的取值
print('**',iter_l.__next__())
print('***',iter_l.__next__())

在for循環中,就是在內部調用了__next__方法才能取到一個一個的值。對象

那接下來咱們就用迭代器的next方法來寫一個不依賴for的遍歷。

l = [1,2,3,4]
l_iter = l.__iter__()
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)

這是一段會報錯的代碼,若是咱們一直取next取到迭代器裏已經沒有元素了,就會拋出一個異常StopIteration,告訴咱們,列表中已經沒有有效的元素了。

這個時候,咱們就要使用異常處理機制來把這個異常處理掉。

l = [1,2,3,4]
l_iter = l.__iter__()
while True:
    try:
        item = l_iter.__next__()
        print(item)
    except StopIteration:
        break

那如今咱們就使用while循環實現了本來for循環作的事情,咱們是從誰那兒獲取一個一個的值呀?是否是就是l_iter?好了,這個l_iter就是一個迭代器。

迭代器遵循迭代器協議:必須擁有__iter__方法和__next__方法。

咱們來看看range()是個啥。首先,它確定是一個可迭代的對象,可是它是不是一個迭代器?咱們來測試一下

print('__next__' in dir(range(12)))  #查看'__next__'是否是在range()方法執行以後內部是否有__next__
print('__iter__' in dir(range(12)))  #查看'__next__'是否是在range()方法執行以後內部是否有__iter__

from collections import Iterator
print(isinstance(range(100000000),Iterator))  #驗證range執行以後獲得的結果不是一個迭代器

 

for循環的本質:循環全部對象,全都是使用迭代器協議。

(字符串,列表,元組,字典,集合,文件對象)這些都不是迭代器,只不過在for循環式,調用了他們內部的__iter__方法,把他們變成了迭代器

而後for循環調用迭代器的__next__方法去取值,並且for循環會捕捉StopIteration異常,以終止迭代

 l=['a','b','c']
 #一:下標訪問方式
 print(l[0])
 print(l[1])
 print(l[2])
 # print(l[3])#超出邊界報錯:IndexError
 
 #二:遵循迭代器協議訪問方式
 diedai_l=l.__iter__()
 print(diedai_l.__next__())
 print(diedai_l.__next__())
 print(diedai_l.__next__())
 # print(diedai_l.__next__())#超出邊界報錯:StopIteration
 
 #三:for循環訪問方式
 #for循環l本質就是遵循迭代器協議的訪問方式,先調用diedai_l=l.__iter__()方法,或者直接diedai_l=iter(l),而後依次執行diedai_l.next(),直到for循環捕捉到StopIteration終止循環
  #for循環全部對象的本質都是同樣的原理
 
 for i in l:#diedai_l=l.__iter__()
     print(i) #i=diedai_l.next()
 
 #四:用while去模擬for循環作的事情
 diedai_l=l.__iter__()
 while True:
     try:
         print(diedai_l.__next__())
     except StopIteration:
         print('迭代完畢了,循環終止了')
         break

 

 

 

 

序列類型:字符串,列表,元組都有下標,你用上述的方式訪問,perfect!可是你可曾想過非序列類型:像字典,集合,文件對象的感覺,因此嘛,年輕人,for循環就是基於迭代器協議提供了一個統一的能夠遍歷全部對象的方法,即在遍歷以前,先調用對象的__iter__方法將其轉換成一個迭代器,而後使用迭代器協議去實現循環訪問,這樣全部的對象就均可以經過for循環來遍歷了

 

二.生成器

Python中提供的生成器:

1.生成器函數:常規函數定義,可是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每一個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行

2.生成器表達式:相似於列表推導,可是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表

 

生成器Generator:

  本質:迭代器(因此自帶了__iter__方法和__next__方法,不須要咱們去實現)

  特色:惰性運算,開發者自定義

 

生成器函數

一個包含yield關鍵字的函數就是一個生成器函數。yield能夠爲咱們從函數中返回值,可是yield又不一樣於return,return的執行意味着程序的結束,調用生成器函數不會獲得返回的具體的值,而是獲得一個可迭代的對象。每一次獲取這個可迭代對象的值,就能推進函數的執行,獲取新的返回值。直到函數執行結束。

import time
def genrator_fun1():
    a = 1
    print('如今定義了a變量')
    yield a
    b = 2
    print('如今又定義了b變量')
    yield b

g1 = genrator_fun1()
print('g1 : ',g1)       #打印g1能夠發現g1就是一個生成器
print('-'*20)   #我是華麗的分割線
print(next(g1))
time.sleep(1)   #sleep一秒看清執行過程
print(next(g1))
初識生成器

 

生成器有什麼好處呢?就是不會一會兒在內存中生成太多數據

假如我想讓工廠給學生作校服,生產2000000件衣服,我和工廠一說,工廠應該是先答應下來,而後再去生產,我能夠一件一件的要,也能夠根據學生一批一批的找工廠拿。
而不能是一說要生產2000000件衣服,工廠就先去作生產2000000件衣服,等回來作好了,學生都畢業了。。

def produce():
    """生產衣服"""
    for i in range(2000000):
        yield "生產了第%s件衣服"%i

product_g = produce()
print(product_g.__next__()) #要一件衣服
print(product_g.__next__()) #再要一件衣服
print(product_g.__next__()) #再要一件衣服
num = 0
for i in product_g:         #要一批衣服,好比5件
    print(i)
    num +=1
    if num == 5:
        break

#到這裏咱們找工廠拿了8件衣服,我一共讓個人生產函數(也就是produce生成器函數)生產2000000件衣服。
#剩下的還有不少衣服,咱們能夠一直拿,也能夠放着等想拿的時候再拿
初識生成器二

 

更多應用

import time


def tail(filename):
    f = open(filename)
    f.seek(0, 2) #從文件末尾算起
    while True:
        line = f.readline()  # 讀取文件中新的文本行
        if not line:
            time.sleep(0.1)
            continue
        yield line

tail_g = tail('tmp')
for line in tail_g:
    print(line)
生成器監聽文件輸入的例子
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count


g_avg = averager()
next(g_avg)
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))
計算移動平均值(1)
def init(func):  #在調用被裝飾生成器函數的時候首先用next激活生成器
    def inner(*args,**kwargs):
        g = func(*args,**kwargs)
        next(g)
        return g
    return inner

@init
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count


g_avg = averager()
# next(g_avg)   在裝飾器中執行了next方法
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))
計算移動平均值(2)_預激協程的裝飾器

yield from

def gen1():
    for c in 'AB':
        yield c
    for i in range(3):
        yield i

print(list(gen1()))

def gen2():
    yield from 'AB'
    yield from range(3)

print(list(gen2()))
yield from

 

列表推導式和生成器表達式

#老男孩因爲峯哥的強勢加盟很快走上了上市之路,alex思來想去決定下幾個雞蛋來報答峯哥

egg_list=['雞蛋%s' %i for i in range(10)] #列表解析

#峯哥瞅着alex下的一筐雞蛋,捂住了鼻子,說了句:哥,你仍是給我只母雞吧,我本身回家下

laomuji=('雞蛋%s' %i for i in range(10))#生成器表達式
print(laomuji)
print(next(laomuji)) #next本質就是調用__next__
print(laomuji.__next__())
print(next(laomuji))
View Code

總結:

1.把列表解析的[]換成()獲得的就是生成器表達式

2.列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存

3.Python不但使用迭代器協議,讓for循環變得更加通用。大部份內置函數,也是使用迭代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用迭代器協議訪問對象,而生成器實現了迭代器協議,因此,咱們能夠直接這樣計算一系列值的和:

sum(x ** 2 for x in xrange(4))

而不用畫蛇添足的先構造一個列表:

sum([x ** 2 for x in xrange(4)]) 

 

本章小結

可迭代對象:

  擁有__iter__方法

  特色:惰性運算

  例如:range(),str,list,tuple,dict,set

迭代器Iterator:

  擁有__iter__方法和__next__方法

  例如:iter(range()),iter(str),iter(list),iter(tuple),iter(dict),iter(set),reversed(list_o),map(func,list_o),filter(func,list_o),file_o

生成器Generator:

  本質:迭代器,因此擁有__iter__方法和__next__方法

  特色:惰性運算,開發者自定義

使用生成器的優勢:

延遲計算,一次返回一個結果。也就是說,它不會一次生成全部的結果,這對於大數據量處理,將會很是有用。

#列表解析
sum([i for i in range(100000000)])#內存佔用大,機器容易卡死
 
#生成器表達式
sum(i for i in range(100000000))#幾乎不佔內存
列表解析式和生成器表達式

有效提升代碼可讀性

相關文章
相關標籤/搜索