12.python迭代器與生成器

迭代器

雙下方法:很少直接調(diào)用的方法, 一般情況下,是通過其他語法觸發(fā)的。
迭代協(xié)議:內(nèi)部實(shí)現(xiàn)了iter方法;
迭代器遵循迭代器協(xié)議:必須擁有iter方法和next方法。
迭代對(duì)象可以通過調(diào)用iter()方法能得到一個(gè)迭代器;
迭代器的特點(diǎn):
1. 方便使用,且只能取所有的數(shù)據(jù)一次;
2. 節(jié)省內(nèi)存空間。

"""
dir([1, 2])是列表中實(shí)現(xiàn)的所有方法,都是以列表的形式返回給我們的,
dir([1, 2].__iter__())是迭代器中實(shí)現(xiàn)的所有方法
set(dir([1, 2]))是轉(zhuǎn)換成集合
然后取差集,就可以看到迭代器特有的方法了
"""
print(set(dir([1,2].__iter__())) - set(dir([1,2])))
iter_1 = [1, 2, 3, 4, 5, 6].__iter__()
#一個(gè)一個(gè)的取值,在for循環(huán)中,就是在內(nèi)部調(diào)用了__next__方法才能取到一個(gè)一個(gè)的值。
# next取到迭代器中沒有元素時(shí)會(huì)拋出一個(gè)異常StopIteration.
iter_1.__next__()
#根據(jù)索引值指定從哪里開始迭代
iter_1.__setstate__(1)
#獲取迭代器中元素的長度
iter_1.__length_hint__()
  1. range()方法
print('__next__' in dir(range(12)))
print('__iter__' in dir(range(12)))
from collections import Iterator
print(isinstance(range(12), Iterator))
輸出:
False
True
False

可以看出range()不是一個(gè)迭代器。

  1. 為什么要有for循環(huán)
    基于上面講的列表這一大堆遍歷方式,聰明的你立馬看除了端倪,于是你不知死活大聲喊道,你這不逗我玩呢么,有了下標(biāo)的訪問方式,我可以這樣遍歷一個(gè)列表啊
l=[1,2,3]

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

#要毛線for循環(huán),要毛線可迭代,要毛線迭代器

沒錯(cuò),序列類型字符串,列表,元組都有下標(biāo),你用上述的方式訪問,perfect!但是你可曾想過非序列類型像字典,集合,文件對(duì)象的感受,所以嘛,年輕人,for循環(huán)就是基于迭代器協(xié)議提供了一個(gè)統(tǒng)一的可以遍歷所有對(duì)象的方法,即在遍歷之前,先調(diào)用對(duì)象的iter方法將其轉(zhuǎn)換成一個(gè)迭代器,然后使用迭代器協(xié)議去實(shí)現(xiàn)循環(huán)訪問,這樣所有的對(duì)象就都可以通過for循環(huán)來遍歷了,而且你看到的效果也確實(shí)如此,這就是無所不能的for循環(huán),覺悟吧,年輕人

生成器

1. 初識(shí)生成器

  • 我們知道的迭代器有兩種:一種是調(diào)用方法直接返回的,一種是可迭代對(duì)象通過執(zhí)行iter方法得到的,迭代器有的好處是可以節(jié)省內(nèi)存。
  • 如果在某些情況下,我們也需要節(jié)省內(nèi)存,就只能自己寫。我們自己寫的這個(gè)能實(shí)現(xiàn)迭代器功能的東西就叫生成器。

3. python中提供的生成器有兩種:

  • 1.生成器函數(shù):常規(guī)函數(shù)定義,但是,使用yield語句而不是return語句返回結(jié)果。yield語句一次返回一個(gè)結(jié)果,在每個(gè)結(jié)果中間,掛起函數(shù)的狀態(tài),以便下次從它離開的地方繼續(xù)執(zhí)行
  • 2.生成器表達(dá)式:類似于列表推導(dǎo),但是,生成器返回按需產(chǎn)生結(jié)果的一個(gè)對(duì)象,而不是一次構(gòu)建一個(gè)結(jié)果列表

生成器的本質(zhì):就是迭代器,自帶了iternext方法;
生成器的特點(diǎn):惰性運(yùn)算,開發(fā)者自定義。

4. 生成器函數(shù)

一個(gè)包含yield關(guān)鍵字的函數(shù)就是一個(gè)生成器函數(shù)。yield可以為我們從函數(shù)中返回值,但是yield又不同于return,return的執(zhí)行意味著程序的結(jié)束,調(diào)用生成器函數(shù)不會(huì)得到返回的具體的值,而是得到一個(gè)可迭代的對(duì)象。每一次獲取這個(gè)可迭代對(duì)象的值,就能推動(dòng)函數(shù)的執(zhí)行,獲取新的返回值。直到函數(shù)執(zhí)行結(jié)束。

import time
def generator_func1():
    a = 1
    print('現(xiàn)在定義了a變量:')
    yield a
    b = 2
    print('現(xiàn)在又定義了b變量:')
    yield b

ge = generator_func1() # 打印g1可以發(fā)現(xiàn)g1就是一個(gè)生成器
print('g1: ', ge)
print('-' * 20)
print(next(ge))
time.sleep(1)
print(next(ge))
#輸出
g1:  <generator object generator_func1 at 0x00000274FDBA96D8>
--------------------
現(xiàn)在定義了a變量:
1
現(xiàn)在又定義了b變量:
2

1. 特點(diǎn):

  1. 調(diào)用的時(shí)候函數(shù)不執(zhí)行,返回一個(gè)生成器;
  2. 調(diào)用next方法的時(shí)候會(huì)取到一個(gè)值;
  3. 直到取完最后一個(gè),在執(zhí)行next會(huì)報(bào)錯(cuò)。

2. 監(jiān)聽文件輸入例子:

def tail(file_name):
    with open(file_name) as fn:
        fn.seek(0, 2)
        while True:
            line = fn.readline()
            if not line:
                time.sleep(1)
                continue
            yield line

tail_g = tail('test.txt')
for line in tail_g:
    print(line)

3. send

def generator():
    print(123)
    content = yield 1
    print('====', content)
    print(456)
    yield 2

g = generator()
ret = next(g) # or g.__next__()
print('----', ret)
ret = g.send('hello')
print('----', ret)

說明:

  1. send 在獲取下一個(gè)值的效果與next一致,但是在使用send之前先要用next。
  2. send可以傳一個(gè)值給yield之前的變量。
  3. 最后一個(gè)yield不能接收外部的值

4.示例

  1. 計(jì)算移動(dòng)平均值
def averager():
    total = 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(50))
# 輸出:
10.0
20.0
30.0
  1. 協(xié)程裝飾器實(shí)現(xiàn)計(jì)算移動(dòng)平均值
def init(func):
    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()
print(g_avg.send(10))
print(g_avg.send(20))
print(g_avg.send(30))

5. 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()))
# 輸出:
['A', 'B', 0, 1, 2]
['A', 'B', 0, 1, 2]
從生成器中取值的幾種方法:
  1. next()
  2. for
  3. 數(shù)據(jù)類型強(qiáng)制轉(zhuǎn)換 list(gen1()) ,不推薦,占用內(nèi)存。

生成器表達(dá)式

laomuji = ('雞蛋%s' %i for i in range(10))
print(laomuji)
print(next(laomuji))
print(laomuji.__next__())
print(next(laomuji))
# 輸出:
雞蛋0
雞蛋1
雞蛋2

總結(jié):

  1. 把列表解析的[]換成()得到的就是生成器表達(dá)式
  2. 列表解析與生成器表達(dá)式都是一種便利的編程方式,只不過生成器表達(dá)式更節(jié)省內(nèi)存
  3. Python不但使用迭代器協(xié)議,讓for循環(huán)變得更加通用。大部分內(nèi)置函數(shù),也是使用迭代器協(xié)議訪問對(duì)象的。例如, sum函數(shù)是Python的內(nèi)置函數(shù),該函數(shù)使用迭代器協(xié)議訪問對(duì)象,而生成器實(shí)現(xiàn)了迭代器協(xié)議,所以,我們可以直接這樣計(jì)算一系列值的和
sum(x ** 2 for x in range(4))

而不用多此一舉的先構(gòu)造一個(gè)列表:

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

3. 總結(jié):

可迭代對(duì)象:
  擁有iter方法
  特點(diǎn):惰性運(yùn)算
  例如: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:
  本質(zhì):迭代器,所以擁有iter方法和next方法
  特點(diǎn):惰性運(yùn)算,開發(fā)者自定義
使用生成器的優(yōu)點(diǎn):

1.延遲計(jì)算,一次返回一個(gè)結(jié)果。也就是說,它不會(huì)一次生成所有的結(jié)果,這對(duì)于大數(shù)據(jù)量處理,將會(huì)非常有用。

#列表解析
sum([i for i in range(100000000)])#內(nèi)存占用大,機(jī)器容易卡死
 
#生成器表達(dá)式
sum(i for i in range(100000000))#幾乎不占內(nèi)存

2.提高代碼可讀性

4. 常見面試題

面試題1:

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. [0, 1, 2, 3]
  []
2. 如果print(list(g1))注釋后輸出 [0, 1, 2, 3]
3. 生成器中的值只能取一次。
import os

def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper

@init
def list_files(target):
    while 1:
        dir_to_search=yield
        for top_dir,dir,files in os.walk(dir_to_search):
            for file in files:
                target.send(os.path.join(top_dir,file))
@init
def opener(target):
    while 1:
        file=yield
        fn=open(file)
        target.send((file,fn))
@init
def cat(target):
    while 1:
        file,fn=yield
        for line in fn:
            target.send((file,line))

@init
def grep(pattern,target):
    while 1:
        file,line=yield
        if pattern in line:
            target.send(file)
@init
def printer():
    while 1:
        file=yield
        if file:
            print(file)

g=list_files(opener(cat(grep('python',printer()))))

g.send('/test1')

協(xié)程應(yīng)用:grep -rl /dir

tail&grep
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容