什么是迭代器

Python 中任意对象,只要定义了next方法,它就是一个迭代器。也可以说 只要是for循环的 都是可迭代对象?为什么说只要可以for就是。因为for语句会调用容器对象中的iter函数

  1. >>> s = 'abc'
  2. >>> it = iter(s)
  3. >>> it
  4. <iterator object at 0x00A1DB50>
  5. >>> next(it)
  6. 'a'
  7. >>> next(it)
  8. 'b'
  9. >>> next(it)
  10. 'c'
  11. >>> next(it)
  12. Traceback (most recent call last):
  13. File "<stdin>", line 1, in <module>
  14. next(it)
  15. StopIteration

什么是生成器

我们知道 在函数体内包含yield 关键字后就不再是一个普通 函数。这种函数叫做生成器
生成器也是一种迭代器,但是你只能通过对其迭代一次。需要的时候再迭代一次。他们的数据不是已经在内存中的,而是在运行的时候生成的值。
其表达形式也很简单 只是将[] 换成() 就可以了。所以我们可以说 生成器表达式可以认为是一种特殊的生成器函数

  1. def read_file_upper(path):
  2. # lines = []
  3. with open(path) as f:
  4. for line in f:
  5. # lines.append(line.upper())
  6. yield line.upper()
  7. # return lines
  8. if __name__ == '__main__':
  9. lines = []
  10. linesUpper = read_file_upper("./test.txt")
  11. # print(linesUpper) # 这个时候生成器处于暂停状态。没有我们指令是不会启动的
  12. # print(next(linesUpper)) # 当使用next后启动状态,结束后等待下一个指令。它会记住自己当前进度
  13. for l in linesUpper:
  14. lines.append(l)
  15. print(lines)
  16. # 这样就将一个简单的函数变成 生成器 将内存使用量降低到最低。
  17. # 单次调用后,next 调用 知道没有数据 会报出 StopIteration 异常

因此 上述代码后我们可以理解迭代器为我们做的事情:

  • 不断调用 next 函数让生成器产出数据;
  • 直到生成器抛出 StopIteration 异常;

思考?为什么生成器可以暂停。使用了什么原理

我们知道,生成器可以利用 yield 语句,将执行权归还给调用者。因此,生成器暂停执行的秘密就隐藏在 yield 语句中
生成器执行进度被保存在 f_lasti 字段

  1. import dis, inspect
  2. frame = None
  3. def foo():
  4. bar()
  5. bar1()
  6. def bar():
  7. global frame
  8. frame = inspect.currentframe()
  9. pass
  10. def bar1():
  11. global frame
  12. frame = inspect.currentframe()
  13. pass
  14. def gen_func():
  15. yield 1
  16. name = 'admin'
  17. yield 2
  18. gender = 'male'
  19. return 3
  20. if __name__ == '__main__':
  21. foo()
  22. print(frame.f_code.co_name)
  23. gen = gen_func()
  24. print(gen.gi_frame.f_lasti) # -1 ,在没有调用next方法迭代时,f_lasti 等于-1, 表示还没开始呢
  25. print(gen.gi_frame.f_locals)
  26. next(gen)
  27. print(gen.gi_frame.f_lasti)
  28. print(gen.gi_frame.f_locals)
  29. next(gen)
  30. print(gen.gi_frame.f_lasti)
  31. print(gen.gi_frame.f_locals)


  • 生成器函数编译后代码对象带有 CO_GENERATOR 标识;
  • 如果函数代码对象带 CO_GENERATOR 标识,被调用时 Python 将创建生成器对象;
  • 生成器创建的同时,Python 还创建一个栈帧对象,用于维护代码对象执行上下文;
  • 调用 next/send 驱动生成器执行,Python 将生成器栈帧对象接入调用链,开始执行字节码;
  • 执行到 yield 语句时,Pythonyield 右边的值放入栈顶,并结束字节码执行循环,执行权回到上一个栈帧;
  • yield 值最终作为 next 函数或 send 方法的返回值,被调用者取得;
  • 再次调用 next/sendPython 重新将生成器栈帧对象接入调用链恢复执行,通过 send 发送的值被放在栈s顶;
  • 生成器函数重新启动后,从 YIELD_VALUE 后的字节码恢复执行,可从栈顶获得调用者发来的值;
  • 代码执行权就这样在调用者和生成器间来回切换,而生成器栈顶被用来传值;

总结:
执行过程;
首先 LOADCONST 将需要带给调用者的 yield 右边的表达式值 加载到运行栈栈顶。
然后 YIELD_VALUE 开始放大招 ,它先从栈顶弹出 yield 的值 这个值作为底层函数 __PyEval_EvalFrameDefault 返回值

紧接着,_PyEval_EvalFrameDefault 函数将当前栈帧 (也就是生成器的栈帧) 从调用链中解开。