1. 什么是迭代协议?

  迭代协议主要包括两方面的协议集,一种是迭代器协议,另一种是可迭代协议。对于迭代器协议来说,其要求迭代器对象在能够在迭代环境中一次产生一个结果。对于可迭代协议来说,就是一个对象序列,该序列可以是实际保存的序列,也可以是按照计算需求而产生的虚拟序列

  在Python中,如何判断一个对象是否可迭代呢?我们可以从collections.abc模块下的IterableIterator得到答案

      

  从图中可以看出, 相比较于Iterator对象,可迭代对象要求实现__iter__()这一魔法方法即可,而一个迭代器对象不仅需要实现__iter__()方法,还需要实现__next__()方法,其作用便是能够迭代环境中惰性地返回一次结果。

2.迭代器对象和可迭代对象

  刚刚我们通过源码分析得出,迭代器对象需要同时拥有__next__()和__iter__()方法,而一个可迭代对象要求实现__iter__()方法。

  那么在Python的数据类型中,那些类型对象是可迭代的呢? 一方面我们可以通过查看所属类型的源码来得出答案,另一方面也可以通过isinstance方法来得知。

  

  在Python的数据类型中, tuple、set、dict、string、list均是可迭代类型。

3.自定义迭代器

  在自定义迭代器之前,我们先来看一段代码

# coding:utf-8
from collections.abc import Iterator, Iterable class MyIterator(): def __init__(self, _list: list):
self._list = _list
self.eq = 0 def __iter__(self):
return self def __next__(self):
"""迭代输出,可能造成eq超出_list的最大下标"""
try:
cur = self._list[self.eq]
except IndexError:
# 超出范围抛出StopIteration异常
raise StopIteration
else:
self.eq += 1
return cur class _Class(object): def __init__(self, members: list):
self.members = members def __iter__(self):
"""实现了可迭代协议,就是一个可迭代对象"""
return MyIterator(self.members) def main():
"""测试"""
students = ["stu[{0}]".format(num) for num in range(1, 11)]
_class = _Class(students)
# 将_class转化为迭代器对象
_class_itor = iter(_class)
print("_class_itor对象是MyIterator的实例:",isinstance(_class_itor, MyIterator))
while 1:
try:
cur = next(_class_itor)
print(cur)
except StopIteration:
print("迭代完成")
break if __name__ == '__main__':
main()

  输出为:

  

  在以上程序中,_Class类拥有__iter__方法便实现了可迭代协议,其对象就是可迭代对象,但是奇怪的是为什么执行iter方法得到的_class_itor对象MyIterator的实例呢?

  我们继续改写代码,使得__iter__返回其自身对象(_Class,只是一个可迭代对象,没有实现__next__方法),看一下效果:

class _Class(object):

    def __init__(self, members: list):
self.members = members def __iter__(self):
"""实现了可迭代协议,就是一个可迭代对象"""
# return MyIterator(self.members)
return self def main():
"""测试"""
students = ["stu[{0}]".format(num) for num in range(1, 11)]
_class = _Class(students)
# 将_class转化为迭代器对象
_class_itor = iter(_class)
print("_class_itor对象是MyIterator的实例:",isinstance(_class_itor, MyIterator))
while 1:
try:
cur = next(_class_itor)
print(cur)
except StopIteration:
print("迭代完成")
break if __name__ == '__main__':
main()

  运行输出:

  

  意料之中,iter函数要求接收的参数对象必须是一个迭代器类型,即实现了__next__方法,在上述代码中,_Class类型只属于可迭代类型,并没有实现__next__方法,所以报错了。

  那么按照上述说法,我们继续改写程序,让_Class的__iter__方法返回一个迭代器对象呢?

  

class _Class(object):

    def __init__(self, members: list):
self.members = members def __iter__(self):
"""实现了可迭代协议,就是一个可迭代对象"""
# return MyIterator(self.members)
return iter(["stu[{0}]".format(num) for num in range(1, 11)]) def main():
"""测试"""
students = ["stu[{0}]".format(num) for num in range(1, 11)]
_class = _Class(students)
# 将_class转化为迭代器对象
_class_itor = iter(_class)
print("_class_itor对象是MyIterator的实例:",isinstance(_class_itor, MyIterator))
while 1:
try:
cur = next(_class_itor)
print(cur)
except StopIteration:
print("迭代完成")
break if __name__ == '__main__':
main()

  运行输出:

  

  由此,我们得出结论:迭代器类型需要实现__next__方法和__iter__方法,并且特别注意的是__iter__方法的返回值需要是Iterator类型, __next__方法只是暂存了迭代器对象在当次迭代环境下的当次结果。

4. 生成器函数

  在分析生成器函数之前,先来一段代码

import builtins
def gen_func():
yield 1
yield 2
yield 3
yield 4
yield 5 def func():
return 1 if __name__ == '__main__':
gen = gen_func()
print(gen)
print(hasattr(gen, "__next__"))
print(hasattr(gen, "__iter__"))
fun = func()
print(fun)

  运行结果:

  

  可以看出,gen_func()函数多次使用yield关键字来惰性地抛出数值,但gen不再是一个int类型,而是一个generator对象, 这便是一个生成器函数(yield作为返回关键字而不采用return)。除此之外,还可以发现generator对象实现了__iter__和__next__方法,也就是说生成器函数对象属于迭代器类型,那么生成器函数对象肯定能够使用迭代语句进行惰性地获取结果。

  

5.用生成器函数惰性实现斐波拉契序列

  1. 采用普通形式(递归地方式,缺点是不能看到完整的数列)

def fib(index):
"""1, 1, 2, 3, 5, 8......."""
if index<=2:
return 1
return fib(index-1) + fib(index-2) if __name__ == '__main__':
print(fib(8)) #21

  

  2. 改进代码,使用列表来存储(采用循环的方式, 缺点是列表的存储空间是有上限的,十分消耗内存)

def fib2(index):
"""1, 1, 2, 3, 5, 8......."""
res = list()
cur_index, ppre, pre = 0, 0, 1
while cur_index<index:
res.append(pre)
ppre, pre = pre, pre+ppre
cur_index += 1
return res if __name__ == '__main__':
print(fib2(8)) # [1, 1, 2, 3, 5, 8, 13, 21]

  

  3. 生成器函数实现(有效解决内存问题,即用即取,惰性操作)

def fib3(index):
cur_index, ppre, pre = 0, 0, 1
while cur_index<index:
yield pre
ppre, pre = pre, ppre+pre
cur_index += 1 if __name__ == '__main__':
for value in fib3(8):
print(value)

  结果:

  

6. 使用生成器函数实现大文件(百GB级,且数据未分行)的读取

  对于大文件的读取,首先考虑的是避免将结果集一次性加载到内存,如果待读取文件是格式按照预先设定规则来进行换行的,如下:

  

  这样的文件直接采用迭代方式读取(for line in file)即可,但如果该文件如下且容量为1TB的该如何逐条读取SQL语句呢?

  

  这时候yield关键字就派上用场了。

  我们可以做如下思考:

  1)文件句柄的read方法,能够接受一个参数作为输出流的缓冲区大小。

  2)循环读取一定容量的数据进行处理,并在每次迭代环境下将一条完整的insert语句抛出。

def yieldLines(file, buffer_size,  flag):
buf = ""
while True:
while flag in buf:
pos = buf.index(flag)
yield buf[:pos]
buf = buf[pos+len(flag):]
chunk = file.read(buffer_size)
# 读取结束
if not chunk:
yield buf
break
buf += chunk if __name__ == '__main__':
with open('./data_insert.txt', 'r') as f:
for line in yieldLines(f, 1024, "|||"):
print(line)

  运行结果:

  

7. 深度分析生成器函数

  在操作系统上有着进程(线程)的概念,在并发(并发是指在一段时间范围内,有多个线程/进程交替被CPU调度;并行是指在一个时间点上,有多个线程/进程被CPU调度,利用的CPU的多核心)执行的时候,程序遇IO操作,为了性能,常见的方式是采用异步非阻塞,但是当IO操作完成,进程由阻塞态转为就绪态时,依据于PCB(进程控制块),其进程能够记住运行上下文,达到继续从阻塞位置之后执行程序代码的效果。

  对于生成器函数而言,其内部也维持了这样一个运行状态的记录,下面我们来对其进行分析

import dis

def gen_func():
a = 1
yield a
b = 2
yield b
c =3
yield c if __name__ == '__main__':
gen = gen_func()
print(dis.dis(gen))

  运行结果如下,显示的是该生成器函数的字节码形式:

  

  乍一看是不是很像汇编语言的指令,的确也类似这样,Python模块被编译成字节码文件之后才交由python解释器来解释执行,这样的指令就被赋予了新的含义,例如:

  LOAD_FAST一般加载局部变量的值,也就是读取值,用于计算或者函数调用传参等。

  STORE_FAST一般用于保存值到局部变量。

  LOAD_GLOBAL用来加载全局变量,包括指定函数名,类名,模块名等全局符号。

  其他指令可以查看这篇博文来获取

  

  有了以上字节码形式,我们可以清楚的了解到函数被解释的细致流程,生成器函数运行状态上下文的记录便依据上图,下面我们来分析一下:

def gen_func():
a = 1
yield a
b = 2
yield b
c =3
yield c if __name__ == '__main__':
gen = gen_func()
# print(dis.dis(gen))
for _ in gen:
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)

  运行结果如下:

  

  通过比较上图两张截图可以看出gi_frame.f_lasti记录了每次python解释器执行YIELD_VALUE指令时的状态编码,来确定生成器函数的运行上下文。

  

浅析python迭代器及生成器函数的更多相关文章

  1. python迭代器与iter()函数实例教程

    python迭代器与iter()函数实例教程 发布时间:2014-07-16编辑:脚本学堂 本文介绍了python迭代器与iter()函数的用法,Python 的迭代无缝地支持序列对象,而且它还允许程 ...

  2. Python 迭代器和生成器(转)

    Python 迭代器和生成器 在Python中,很多对象都是可以通过for语句来直接遍历的,例如list.string.dict等等,这些对象都可以被称为可迭代对象.至于说哪些对象是可以被迭代访问的, ...

  3. 一文搞懂Python迭代器和生成器

    很多童鞋搞不懂python迭代器和生成器到底是什么?它们之间又有什么样的关系? 这篇文章就是要用最简单的方式让你理解Python迭代器和生成器! 1.迭代器和迭代过程 维基百科解释道: 在Python ...

  4. Python - 迭代器与生成器 - 第十三天

    Python 迭代器与生成器 迭代器 迭代是Python最强大的功能之一,是访问集合元素的一种方式. 迭代器是一个可以记住遍历的位置的对象. 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问 ...

  5. Python入门篇-生成器函数

    Python入门篇-生成器函数 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.生成器概述 1>.生成器generator 生成器指的是生成器对象,可以由生成器表达式得到, ...

  6. 怎么理解Python迭代器与生成器?

    怎么理解Python迭代器与生成器?在Python中,使用for ... in ... 可以对list.tuple.set和dict数据类型进行迭代,可以把所有数据都过滤出来.如下:         ...

  7. python中的生成器函数是如何工作的?

    以下内容基于python3.4 1. python中的普通函数是怎么运行的? 当一个python函数在执行时,它会在相应的python栈帧上运行,栈帧表示程序运行时函数调用栈中的某一帧.想要获得某个函 ...

  8. python迭代器和生成器(3元运算,列表生成式,生成器表达式,生成器函数)

    1.1迭代器 什么是迭代器: 迭代器是一个可以记住遍历的位置对象 迭代器对象从集合的第一个元素元素开始访问,直到所有元素被访问完结束,迭代器只能往前不会后退. 迭代器有两个基本方法:iter ,nex ...

  9. 【python基础】迭代器和生成器函数

    1.迭代器协议: 1.迭代器协议是指:对象必须提供一个 __next__() 方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代(只能往后走不能往前退) ...

随机推荐

  1. Jmeter学习笔记(十五)——常用的4种参数化方式

    一.Jmeter参数化概念 当使用JMeter进行测试时,测试数据的准备是一项重要的工作.若要求每次迭代的数据不一样时,则需进行参数化,然后从参数化的文件中来读取测试数据. 参数化是自动化测试脚本的一 ...

  2. 昨日万圣节ABAP怪兽级代码谜团,公布答案啦

    首先非常感谢大家在周末还抽出宝贵的时间耗在Jerry昨天发布的文章 一段让人瑟瑟发抖的ABAP代码 上面. 虽然Jerry在文末开玩笑的声称,只有文章阅读量上千或者评论数超过50,才公布答案.其实这只 ...

  3. BBPlus团队ALPFA冲刺(肖文恒)

    ALPHA冲刺博客 第一天:https://www.cnblogs.com/bbplus/p/11931039.html 第二天:https://www.cnblogs.com/bbplus/p/11 ...

  4. Mac 下 安装Python3

    因为Mac系统自带Python2.7 所以我们开发要重新装Python3 直接运行下面就好 luohaotiandeMacBook-Pro:~ luohaotian$ which python /us ...

  5. 打造kubernetes 高可用集群(nginx+keepalived)

    一.添加master 部署高可用k8s架构 1.拷贝/opt/kubernetes目录到新的master上(注意如果新机上部署了etcd要排除掉) scp -r /opt/kubernetes/ ro ...

  6. zabbix Server 4.0 监控JMX监控详解

    zabbix Server 4.0 监控JMX监控详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   大家都知道,zabbix server效率高是使用C语言编写的,有很多应用 ...

  7. 2013.4.26 - KDD第八天

    下午上Android课,我看中秋也选这个课了,然后在上半节的时候速补了一下秦海龙师兄的那篇文章.中间休息的时候窜到了中秋那里,然后讨论了半节课现在的情况. 现在的情况是这样的: 中 秋开始是没有进行主 ...

  8. Java中创建线程主要有三种方式

    一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行体. (2)创建Thread子类的实 ...

  9. 关于使用scipy.stats.lognorm来模拟对数正态分布的误区

    lognorm方法的参数容易把人搞蒙.例如lognorm.rvs(s, loc=0, scale=1, size=1)中的参数s,loc,scale, 要记住:loc和scale并不是我们通常理解的对 ...

  10. 抖音热门BGM爬虫下载

    下午无聊在某网上刷了会儿抖音,发现有些音乐还是挺好听的,可以用来做手机铃声,于是想办法从某网上把歌曲爬下来 附上代码: #!/usr/bin/env python # -*- coding: utf- ...