迭代器
双下方法:很少直接调用的方法, 一般情况下,是通过其他语法触发的。
迭代协议:内部实现了iter方法;
迭代器遵循迭代器协议:必须拥有iter方法和next方法。
迭代对象可以通过调用iter()方法能得到一个迭代器;
迭代器的特点:
1. 方便使用,且只能取所有的数据一次;
2. 节省内存空间。
"""
dir([1, 2])是列表中实现的所有方法,都是以列表的形式返回给我们的,
dir([1, 2].__iter__())是迭代器中实现的所有方法
set(dir([1, 2]))是转换成集合
然后取差集,就可以看到迭代器特有的方法了
"""
print(set(dir([1,2].__iter__())) - set(dir([1,2])))
iter_1 = [1, 2, 3, 4, 5, 6].__iter__()
#一个一个的取值,在for循环中,就是在内部调用了__next__方法才能取到一个一个的值。
# next取到迭代器中没有元素时会抛出一个异常StopIteration.
iter_1.__next__()
#根据索引值指定从哪里开始迭代
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()不是一个迭代器。
- 为什么要有for循环
基于上面讲的列表这一大堆遍历方式,聪明的你立马看除了端倪,于是你不知死活大声喊道,你这不逗我玩呢么,有了下标的访问方式,我可以这样遍历一个列表啊
l=[1,2,3]
index=0
while index < len(l):
print(l[index])
index+=1
#要毛线for循环,要毛线可迭代,要毛线迭代器
没错,序列类型字符串,列表,元组都有下标,你用上述的方式访问,perfect!但是你可曾想过非序列类型像字典,集合,文件对象的感受,所以嘛,年轻人,for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的iter方法将其转换成一个迭代器,然后使用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了,而且你看到的效果也确实如此,这就是无所不能的for循环,觉悟吧,年轻人
生成器
1. 初识生成器
- 我们知道的迭代器有两种:一种是调用方法直接返回的,一种是可迭代对象通过执行iter方法得到的,迭代器有的好处是可以节省内存。
- 如果在某些情况下,我们也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器。
3. python中提供的生成器有两种:
- 1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行
- 2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器的本质:就是迭代器,自带了iter和next方法;
生成器的特点:惰性运算,开发者自定义。
4. 生成器函数
一个包含yield关键字的函数就是一个生成器函数。yield可以为我们从函数中返回值,但是yield又不同于return,return的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。
import time
def generator_func1():
a = 1
print('现在定义了a变量:')
yield a
b = 2
print('现在又定义了b变量:')
yield b
ge = generator_func1() # 打印g1可以发现g1就是一个生成器
print('g1: ', ge)
print('-' * 20)
print(next(ge))
time.sleep(1)
print(next(ge))
#输出
g1: <generator object generator_func1 at 0x00000274FDBA96D8>
--------------------
现在定义了a变量:
1
现在又定义了b变量:
2
1. 特点:
- 调用的时候函数不执行,返回一个生成器;
- 调用next方法的时候会取到一个值;
- 直到取完最后一个,在执行next会报错。
2. 监听文件输入例子:
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 在获取下一个值的效果与next一致,但是在使用send之前先要用next。
- send可以传一个值给yield之前的变量。
- 最后一个yield不能接收外部的值
4.示例
- 计算移动平均值
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
- 协程装饰器实现计算移动平均值
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
- 数据类型强制转换 list(gen1()) ,不推荐,占用内存。
生成器表达式
laomuji = ('鸡蛋%s' %i for i in range(10))
print(laomuji)
print(next(laomuji))
print(laomuji.__next__())
print(next(laomuji))
# 输出:
鸡蛋0
鸡蛋1
鸡蛋2
总结:
- 把列表解析的[]换成()得到的就是生成器表达式
- 列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
- Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和
sum(x ** 2 for x in range(4))
而不用多此一举的先构造一个列表:
sum([x ** 2 for x in range(4)])
3. 总结:
可迭代对象:
拥有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方法
特点:惰性运算,开发者自定义
使用生成器的优点:
1.延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。
#列表解析
sum([i for i in range(100000000)])#内存占用大,机器容易卡死
#生成器表达式
sum(i for i in range(100000000))#几乎不占内存
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')
协程应用:grep -rl /dir
tail&grep