Python之迭代器、生成器
1、列表生成式
# 普通生成列表 ab=[] for x in range(10): ab.append(x*x) print(ab) # 列表表达式 a=[x*x for x in range(10)] # 这就叫列表生成式,也叫声明式编程 ''' 1:先取元素 2:把取出来的元素进行一个处理 x*x 3:把处理完的元素依次存放到列表里 print(a) ''' egg=[x for x in range(100) if x<50] # 如果 x 小于 50,就加进去 egg 的列表 print(egg) 缺点: 如果可迭代的对象很大,会把可迭代的内容全放到内存处理,占满内存,解决这个问题用生成器表达式
2、生成器表达式
#生成器表达式 egg = (x for x in range(100) if x < 50) # 这就叫生成器表达式,与列表生成式的区别就是用小括号 print(egg) # <generator object <genexpr> at 0x000001FCDB352E60> 生成器对象 # 如果想要拿里面的值通过 for 循环,或者 next 方法 for i in egg: print(i) print(next(egg)) print(next(egg)) print(next(egg)) print(next(egg)) ''' 跟列表生成式的区别 列表生成式有索引,取值灵活,能获取长度 生成器表达式省内存,用一个取一个,不能获取长度,没有索引 '''
3、迭代器
''' 迭代器协议满足两个条件 1:对象可以被__iter__方法转换成迭代器的,或 iter(obj) 2:对象可以通过__next__方法取值的,或 next(obj) ''' l=[1,2,3,5] # 列表是一个可迭代对象,不是迭代器,有 iter 方法 d=iter(l) # 或者 l.__iter__(),把列表用 iter 函数或__iter__()方法,转换为一个迭代器 print(d) # <list_iterator object at 0x0000000000B16B38> # 优点: ''' 1:迭代器提供了一种不依赖索引的取值方式,这样就可以遍历那种没有索引的可迭代对象(字典,集合,文件) 2:迭代器与列表比较,迭代器是惰性计算的,更节省内存,什么叫惰性计算,列表可迭代对象,和迭代器比较,可以知道可迭代对象的长度len方法,但无法知道迭代器的长度,迭代器每next 只拿一次数据 ''' # 缺点: ''' 一次性的,只能往后取值,不能倒着取值 '''
取迭代器里的内容
print(d.__length_hint__()) # 获取迭代器元素的长度 d.__setstate__(2) # 设置迭代器索引值的开始 print(next(d)) # 用 next 方法取迭代器对象里的值 print(next(d)) print(next(d)) # 报错,为什么,看下面 while 和 for while True: try: print(next(d)) # 循环取迭代器里的内容 except StopIteration: # 去捕捉 StopIteration 的错误 break # 一旦捕捉到,就退出 for i in d: print(i) ''' for 循环内部一共做了三件事 1:调用可迭代对象(d)的 __iter__ 方法返回一个迭代器对象 2:不断调用迭代器对象的 next 方法,去取值 3:处理 stopiteration 异常,原理跟上面的 while 差不多 ''' from collections import Iterator,Iterable print(isinstance(l,Iterable)) 判断是否为可迭代对象 iterator 迭代器 iterable 可迭代 # 格式 isinstance(对象,类型) isinstance方法:判断对象是否为指定的类型,是返回 True,否返回 False
4、生成器
注意:生成器都是迭代器,迭代器不一定是生成器
# 创建生成器:
a = (x*2 for x in range(5)) # 通过生成器表达式创建生成器 # 函数里面有 yield 关键字表示这个函数是一个生成器,其内部是把函数做成迭代器 def foo(): print('ok') yield 1 # 返回值,类似 return,返回给 next() print('ok2') yield 2 # 返回值,类似 return g=foo() # foo() 返回一个生成器对象 print(g) #<generator object foo at 0x000000000083A518> next(g) # 显示的是 ok:返回值是 1,生成器也是迭代器,通过 next 方法取值 next(g) # 显示的是 ok2:返回值 2 # a=next(g) # 1 ,屏幕会输出 ok # b=next(g) # 2 ,屏幕会输出 ok2 # print(a,b) for 循环过程 for i in foo(): while True: i=next(foo()) #先执行 next(foo())返回 print(ok),然后把 yield 的值返回给 i,print(i)=1 ok 1 ok2 2
# 生成器就是一个可迭代对象(iterable)
生成器自动实现了迭代器协议
for i in a: print(i)
# for 会对 a 对象进行一个 next 的方法调用,循环取一个值,取第二个值,第一个值没有变量引用,会被内存垃圾回收了,达到节省内存的目的
# 生成器 yield 与 函数 return有何区别?
return只能返回一次函数就彻底结束了,而yield能返回多次值
# yield到底干了什么事情:
yield 把 __iter__ 和 __next__ 方法封装到函数内部使其变成生成器-->迭代器
用return返回值能返回一次,而yield返回多次
函数在暂停以及继续下一次运行时的状态是由yield保存
send 方法
def bar(): print('ok') count=yield 1 print(count) print('ok2') yield 2 b=bar() #生成一个生成器对象 ret=b.send(None) #b.send(None)=next(b) 第一次 send 前如果没有 next,只能传一个 None,并返回 yield 的值;这里赋给 ret,或者先 next(b) 执行一下,也可以写一个初始化生成器的装饰器 print(ret) # ret 的值为 yield 的值:1 ret2=b.send('eee') #把 eee 赋值给 count,并返回 count 的值 eee print(ret2)
send() 与 next() 的区别
1、如果函数内yield是表达式形式,那么必须先next(e),可以为生成器造一个装饰器让其不用每次都next一遍
2、二者的共同之处是都可以让函数在上次暂停的位置继续运行,不一样的地方在于send在触发下一次代码的执行时,会顺便给yield传一个值
3、send 传多个值得时候要以元祖的形式传过去
生成器例子
菲波那切数列
# def fib(max): # n,before,after=0,0,1 # while n < max: # print(before) # before,after=after,before+after # n+=1 # # fib(2) def fiber(max): n,before,after=0,0,1 while n<max: yield before before,after=after,before+after n+=1 g=fiber(8) # 生成器对象 # print(g) # print(next(g)) # 返回生成器对象的 yield 值 # print(next(g))
生成器练习
通过生成器写一个日志调用方法,支持以下功能
根据指令向屏幕输出日志
根据指令向文件输出日志
根据指令同时向文件&屏幕输出日志
以上日志格式如下
2017-10-19 22:07:38 [1] test log db backup 3
2017-10-19 22:07:40 [2] user alex login success
#注意:其中[1],[2]是指自日志方法第几次调用,每调用一次输出一条日志
def init(func): def warpper(*args, **kwargs): res = func(*args, **kwargs) next(res) return res return warpper @init def logger(filename, channel='terminal'): count = 1 while 1: c = yield 1 current_time = time.strftime('%Y-%m-%d %H:%M:%S') info = '%s [%s] %s' % (current_time, count, c,) if channel == 'terminal': print(info) elif channel == 'file': with open('example.ini', 'a', encoding='utf8') as f: f.write('\n%s' % (info,)) elif channel == 'both': print(info) with open('example.ini', 'a', encoding='utf8') as f: f.write('\n%s' % (info,)) count += 1 lo = logger('abc', channel='both') next(lo) lo.send('aaa') time.sleep(2) lo.send('ccc')
模拟 linux tail 命令
#!/usr/local/python3.5.2/bin/python3.5 def tail_f(file_path): import time with open(file_path,'r') as f: f.seek(0,2) # 光标始终置尾部 while True: for line in f: if not line: # 如果为空就停住 time.sleep(0.3) continue else: yield line.strip() g=tail_f('a.txt') for i in g: print(i)
模拟 linux 的 tail + grep 命令
def tail_f(file_path): import time with open(file_path,'r') as f: f.seek(0,2) while True: for line in f: if not line: time.sleep(0.3) continue else: yield line def grep(partten,lines): for line in lines: if partten in line: yield line g1=tail_f('a.txt') g2=grep('error',g1) for i in g2: print(i.strip())
.