一. python的迭代协议
1.什么是迭代器:
迭代器是访问集合内数据的一种方式,一般是用来遍历数据的
2.迭代器和以下标访问的方式不一样,迭代器是不能返回的,且迭代器提供了一种惰性访问数据的方式
3.python中可迭代的对象基本都是实现了__iter__魔法函数的,迭代协议实质上就是满足__iter__魔法函数
可迭代类型
迭代器
from collections.abc import Iterable,Iterator a=[1,2] #list是可迭代的,但不是一个迭代器(list中实现了__iter__,但是没有实现__next__) print(isinstance(a,Iterable)) print(isinstance(a,Iterator)) ''' 输出: True False '''
二. 什么是迭代器和可迭代对象
1.可迭代和迭代器的区别:
from collections.abc import Iterable,Iterator a=[1,2] #迭代器,内置函数iter()会首先检查是否有__iter__,如果没有就会调用__getitem__首先创建一个迭代器 iter_rator=iter(a) print(isinstance(a,Iterable)) print(isinstance(a,Iterator)) print(isinstance(iter_rator,Iterable)) print(isinstance(iter_rator,Iterator)) ''' 输出: True False True True '''
2.__iter__和__getitem__:
class Comany(object): def __init__(self, employee_list): self.employee_list = employee_list def __iter__(self): return 1 def __getitem__(self, item): return self.employee_list[item] if __name__ == '__main__': com = Comany(['LYQ1', 'LYQ2', 'LYQ3']) #内部实质也是iter,首先调用__iter__(如果有),否则调用__getitem for i in com: print(i)
__iter__和__getitem__都实现,首先调用__iter__
只实现__getitem__
__iter__和__getitem__都不实现
3.自己实现迭代器:
class Comany(object): def __init__(self, employee_list): self.employee_list = employee_list def __iter__(self): return MyIterator(self.employee_list) class MyIterator(Iterator): def __init__(self, emp_list): self.iter_list = emp_list self.index = 0 def __next__(self): # 真正返回迭代值的逻辑,迭代器不支持切片,但__getitem__支持且可以取任意位置的数 try: word = self.iter_list[self.index] except IndexError: raise StopIteration self.index += 1 return word if __name__ == '__main__': com = Comany(['LYQ1', 'LYQ2', 'LYQ3']) my_itor = iter(com) # for循环内部其实也是调用next放法 while True: try: # my_itor为一个迭代器,实际在__next__中实现 print(next(my_itor)) except StopIteration: pass
三. 生成器函数使用
1.什么是生成器函数:函数里只要有yeild关键字:
生成器函数返回的是一个生成器对象,是在python编译字节码的时候就产生了;
return只能有一个返回值,而yield可以返回多个,生成器对象实现了迭代协议;
惰性求值,延迟求值提供了可能。
def gen_fun(): #首先把值返回给调用方 #然后又调用__next__调用下一个(实现了生成器协议) yield 'a' yield 'b' def fun(): return 1 #生成器函数返回的是一个生成器对象,实在python编译字节码的时候就产生了 #return只能有一个返回值,而yield可以返回多个 c1 = gen_fun() c2 = fun() print(c1) for i in c1: print(i) print(c2) ''' <generator object gen_fun at 0x000001AF4399D048> a b 1 '''
2.例:
使用return:
#斐波拉契过程 def fib(index): re_list=[] n,a,b=0,0,1 while n<index: re_list.append(b) a,b=b,a+b n+=1 return re_list #若果index太大,十分消耗内存,这时就可以用到yield print(fib(10)) ''' 输出: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] '''
使用生成器:
#使用生成器的斐波拉契过程 def gen_fib(index): n,a,b=0,0,1 while n<index: #将值yield,不会消耗内存 yield b a,b=b,a+b n+=1 for i in gen_fib(10): print(i)
四. 生成器的原理
1.函数工作原理:
python.exe会用一个叫做PyEval_EvalFrameEx(c语言函数)去执行函数,首先会创建一个栈帧(stack frame)
def foo(): bar() def bar(): pass #python.exe会用一个叫做PyEval_EvalFrameEx(c语言函数)去执行foo()函数,首先会创建一个栈帧(stack frame) """ python一切皆对象,栈帧对象, 字节码对象 当foo调用子函数 bar, 又会创建一个栈帧 所有的栈帧都是分配在堆内存上,这就决定了栈帧可以独立于调用者存在 """
def foo(): bar() def bar(): pass #查看字节码,以下为查看foo函数的字节码 import dis print(dis.dis(foo)) ''' 2 0 LOAD_GLOBAL 0 (bar) ——》加载目标函数 2 CALL_FUNCTION 0 ——》执行函数 4 POP_TOP ——》从栈顶端pop出 6 LOAD_CONST 0 (None)——》加载return值,没有就None 8 RETURN_VALUE ——》将值return '''
import inspect frame=None def foo(): bar() def bar(): global frame frame=inspect.currentframe() foo() #即使函数运行完成,也可以查看函数的栈帧,是放在堆上面的,和静态语言不一样(放在栈上,运行后就销毁了) print(frame.f_code.co_name) caller_frame=frame.f_back print(caller_frame.f_code.co_name) ''' 输出: bar foo '''
2.生成器工作原理:(生成器对象也是放在堆内存中的,可以独立于调用者存在,任何地方都可以控制它)
函数工作原理
对生成器对象封装,f_lasti: 最近执行代码字节码的位置,以及f_locals:变量,因为有这两个变量,就可以不断循环函数
def gen_fun(): #生成器函数可以return一个值,早期的python不行 #编译字节码时,识别了关键词yield,标记函数 yield 1 name='LYQ' yield 2 age=20 return 'HaHa' import dis gen=gen_fun() print(dis.dis(gen)) print(gen.gi_frame.f_lasti) print(gen.gi_frame.f_locals) next(gen) print(gen.gi_frame.f_lasti) print(gen.gi_frame.f_locals) next(gen) print(gen.gi_frame.f_lasti) print(gen.gi_frame.f_locals) ''' 输出: 4 0 LOAD_CONST 1 (1) 2 YIELD_VALUE 4 POP_TOP 5 6 LOAD_CONST 2 ('LYQ') 8 STORE_FAST 0 (name) 6 10 LOAD_CONST 3 (2) 12 YIELD_VALUE 14 POP_TOP 7 16 LOAD_CONST 4 (20) 18 STORE_FAST 1 (age) 8 20 LOAD_CONST 5 ('HaHa') 22 RETURN_VALUE None -1——》还没开始执行 {} 2 ——》执行到第一个yield的位置(yield 1) {} 12——》执行到第二个yield的位置(yield 2) {'name': 'LYQ'}——》把局部变量拿出来了 '''
五. 通过UserList来看生成器的应用
实现了遍历list,i是局部变量,从0开始,一步一步返回值
六. 生成器实现大文件读取
#读取大文件 def myreadlines(f, newline): #buf相当于缓存 buf = "" while True: while newline in buf: pos = buf.index(newline) yield buf[:pos] #更新截断buf(以分割符) buf = buf[pos + len(newline):] #每次读取4096个字符(接着上一次的) chunk = f.read(4096) if not chunk: #说明已经读到了文件结尾 yield buf break buf += chunk with open("input.txt") as f: for line in myreadlines(f, "{|}"): print (line)