迭代器
雙下方法:很少直接調(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__()
- 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è)迭代器。
- 為什么要有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ì):就是迭代器,自帶了iter和next方法;
生成器的特點(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):
- 調(diào)用的時(shí)候函數(shù)不執(zhí)行,返回一個(gè)生成器;
- 調(diào)用next方法的時(shí)候會(huì)取到一個(gè)值;
- 直到取完最后一個(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)
說明:
- send 在獲取下一個(gè)值的效果與next一致,但是在使用send之前先要用next。
- send可以傳一個(gè)值給yield之前的變量。
- 最后一個(gè)yield不能接收外部的值
4.示例
- 計(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
- 協(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]
從生成器中取值的幾種方法:
- next()
- for
- 數(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é):
- 把列表解析的[]換成()得到的就是生成器表達(dá)式
- 列表解析與生成器表達(dá)式都是一種便利的編程方式,只不過生成器表達(dá)式更節(jié)省內(nèi)存
- 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