迭代器和生成器

迭代器和生成器

迭代和可迭代

什麼是迭代(iteration)? 
若是給定一個list或tuple,咱們要想訪問其中的某個元素,咱們能夠經過下標來,若是咱們想要訪問全部的元素,那咱們能夠用for循環來遍歷這個list或者tuple,而這種遍歷咱們就叫作迭代。面試

可迭代(iterable)?編程

其實你已經知道,不是全部的數據類型都是可迭代的。那麼可迭代的數據類型都有什麼特色呢?app

print(dir([1,2]))
print(dir((2,3)))
print(dir({1:2}))
print(dir({1,2}))

['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
結果

總結一下咱們如今所知道的:能夠被for循環的都是可迭代的,要想可迭代,內部必須有一個__iter__方法。迭代器協議ssh

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('__iter__' in dir([l]))
print(isinstance(t,Iterable))
print('__iter__' in dir([t]))
print(isinstance(d,Iterable))
print('__iter__' in dir([d]))
print(isinstance(s,Iterable))
print('__iter__' in dir([s]))
判斷是否能夠迭代

接着分析,__iter__方法作了什麼事情呢?ide

print([1,2].__iter__())

結果
<list_iterator object at 0x1024784a8>

執行了list([1,2])的__iter__方法,咱們好像獲得了一個list_iterator,如今咱們又獲得了一個新名詞——iterator,即迭代器。函數

迭代器  

咱們調用可迭代對象的__iter__方法以後,獲得了一個新東西--迭代器。post

那迭代器是什麼呢?請繼續日後閱讀吧。測試

迭代器協議  

既什麼叫「可迭代」以後,又一個歷史新難題,什麼叫「迭代器」?spa

雖然咱們不知道什麼叫迭代器,可是咱們如今已經有一個迭代器了,這個迭代器是一個列表的迭代器。3d

咱們來看看這個列表的迭代器比起列表來講實現了哪些新方法,這樣就能揭開迭代器的神祕面紗了吧?

'''
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__())

這三個方法中,能讓咱們一個一個取值的神奇方法是誰?

沒錯!就是__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__方法。

還帳:next和iter方法

如此一來,關於迭代器和生成器的方法咱們就還清了兩個,最後咱們來看看 range()是個啥。首先,它確定是一個可迭代的對象,可是它是不是一個迭代器?咱們來測試一下

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

from collections import Iterator
print(isinstance(range(100000000),Iterator))  #驗證range執行以後獲得的結果不是一個迭代器
range函數的返回值是一個可迭代對象

爲何要有for循環   

基於上面講的列表這一大堆遍歷方式,聰明的你立馬看除了端倪,因而你不知死活大聲喊道,你這不逗我玩呢麼,有了下標的訪問方式,我能夠這樣遍歷一個列表啊

 

l=[1,2,3]

index=0
while index < len(l):
    print(l[index])
    index+=1

#要毛線for循環,要毛線可迭代,要毛線迭代器

 

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

生成器  

初識生成器  

咱們知道的迭代器有兩種:一種是調用方法直接返回的,一種是可迭代對象經過執行 iter方法獲得的,迭代器有的好處是能夠節省內存。

若是在某些狀況下,咱們也須要節省內存,就只能本身寫。咱們本身寫的這個能實現迭代器功能的東西就叫生成器。

 

Python 中提供的 生成器:

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

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

 

生成器Generator:

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

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

生成器函數  

一個包含yield關鍵字的函數就是一個生成器函數。

yield和return同樣能夠從函數中返回值,可是yield又不一樣於return,return的執行意味着程序的結束,只能返回一次,yield能夠返回屢次。

調用生成器函數不會獲得返回的具體的值,而是獲得一個生成器對象。

每一次從這個可迭代對象獲取值,就能推進函數的執行,獲取新的返回值。直到函數執行結束(yield像是擁有可以讓函數暫停的魔力)。

def my_range():
    print('我是一個生成器函數')
    n = 0
    while 1:
        yield n
        n += 1

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

接下來,咱們在這個函數的基礎上來寫一個咱們本身的range函數,實現開始和結束

def my_range2(start, stop):
    n = start
    while n < stop:
        yield n
        n += 1

再進一步,實現步長:

def my_range3(start, stop, step):
    n = start
    while n < stop:
        yield n
        n += step

生成器本質上就是個迭代器,咱們根據本身的想法創造的迭代器,它固然也支持for循環:

for i in my_range3(1, 10, 2):
    print(i)

更多應用

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)
生成器監聽文件輸入的例子

send

yield能夠返回值,也能夠接收值。

經過生成器的send方法能夠給yield傳值。

def eat(name):
    print('%s要開始吃了!' % name)
    while 1:
        food = yield
        print('{}在吃{}'.format(name, food))


a = eat('alex')
a.__next__()  # 初始化,讓函數暫停在yield處
a.send('包子')  # send兩個做用:1.給yield傳值 2.繼續執行函數
a.send('餃子')

yield能夠同時返回值和接收值。

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
View Code

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

#老男孩因爲峯哥的強勢加盟很快走上了上市之路,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))
峯哥與alex的故事

總結:

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

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

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

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

 

生成器相關的面試題  

def demo():
    for i in range(4):
        yield i

g=demo()

g1=(i for i in g)
g2=(i for i in g1)

print(list(g1))
print(list(g2))
面試題1
def add(a, b):
    return a + b


def func():
    for i in range(4):
        yield i


g = func()
for n in [1, 10]:
    g = (add(n, i) for i in g)


print(list(g))
面試題2
相關文章
相關標籤/搜索