python的迭代协议

引言

迭代器是访问集合内部元素的一种方式,一般用来遍历数据。
迭代器和用下标索引访问的方式不一样,迭代器是不能直接返回值的。
迭代器提供了一种惰性访问数据的方式,需要的时候才产生数据。
可迭代类型都实现了迭代协议,实际上就是iter()这个魔法函数。
可迭代类型和迭代器

前面讲过,collections.abc模块中定义了很多内置的抽象基类,现在我们重点关注其中的两个:Iterable 和 Iterator

Iterable

里面定义了一个抽象方法,iter(),也就是说某个类只要实现了这个魔法函数,它就是可迭代的类型

Iterator

首先,Iterator继承了Iterable,在它的基础上,又增加了一个抽象方法:next(),它是用来让迭代器获取下一个元素。

小结

可迭代类型和迭代器并不一样,前者只需要实现iter()函数,而对后者而言,next()才是它的核心。

比如list类型,它是一个可迭代类型,但并不是一个迭代器。

a = [1, 2, 3]
print(isinstance(a, Iterable), isinstance(a, Iterator))

result:

True False

补充

在魔法函数那一小节,我们讲过这样一个例子:

class Language(object):
def init(self, language_list):
self.lans = language_list

def getitem(self, item):
return self.lans[item]
language = Language(["Python", "C", "Lisp"])
for lan in language:
print(lan)

result:

Python

C

Lisp

在Language这个类中,我们定义的是getitem这个魔法函数,然后对这个类产生的实例我们可以使用for来遍历元素了。也就是说它成为了一个可迭代类型,但它并没有实现刚才我们讨论的iter()函数。

这是因为,在Python内部,很多地方做了兼容处理,当我们是用for进行迭代遍历,解释器首先会寻找iter()函数,如果没有,它就会退一步去寻找getitem(),这个是序列类型中会实现的一个魔法函数,只要它接收从 0 开始的整数为参数,这个对象也是会被当做可迭代类型的。

实际上,仅仅满足了可迭代类型还不够,真正能进行迭代取值的是迭代器。通过iter()函数,我们可以返回一个可迭代对象的迭代器,有了它才能进行迭代取值。

class Language(object):
def init(self, language_list):
self.lans = language_list

def getitem(self, item):
return self.lans[item]
language = Language(["Python", "C", "Lisp"])
my_iterator = iter(language)
print(my_iterator)

result:

<iterator object at 0x000002043CEE4F28>

如果我们不实现iter()或getitem(),获取迭代器的过中会出错

class Language(object):
def init(self, language_list):
self.lans = language_list
language = Language(["Python", "C", "Lisp"])
my_iterator = iter(language)
print(my_iterator)

result:

TypeError: 'Language' object is not iterable

有了迭代器,迭代取值需要另外一个函数next(),每调用一次,就会返回一个值,直到抛出一个迭代结束的异常。

class Language(object):
def init(self, language_list):
self.lans = language_list

def getitem(self, item):
return self.lans[item]
language = Language(["Python", "C", "Lisp"])
my_iterator = iter(language)
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))

result:

Python

C

Lisp

StopIteration

上面的结果已经很接近直接使用for进行迭代了,但是,依赖getitem()函数,底层还是隐藏了很多细节,如果我们想纯粹地通过iter()来实现迭代过程,要怎么做呢?

iter()用来返回一个迭代器,通过这个迭代器来迭代取值,对应显示调用iter()的逻辑。

next()用来让迭代器取下一个值,对应显示调用next()的逻辑。

使用for的时候,这两个魔法函数会被自动调用,完成迭代取值过程。

from collections.abc import Iterator
class MyIterator(Iterator):
def init(self, data_list):
self.iter_list = data_list
self.index = 0

def next(self):

这里是通过记录索引,单次取值达到迭代目的

更好的方式是通过生成器来进行迭代取值

try:
data = self.iter_list[self.index]
except IndexError:
raise StopIteration
self.index += 1
return data
class Language(object):
def init(self, language_list):
self.lans = language_list

def iter(self):
return MyIterator(self.lans)

language = Language(["Python", "C", "Lisp"])
for lan in language:
print(lan)

result:

Python

C

Lisp

生成器函数使用

引言

函数里面只要存在yield关键字,它就是生成器函数
生成器是惰性计算的一个关键
使用案例

def gen_func():
yield "MetaTian"

def func():
return "MetaTian"

gen, res = gen_func(), func()
print(gen)
print(res)

result:

<generator object gen_func at 0x000001B102C3C0F8>

MetaTian

for val in gen:
print(val)

result:

MetaTian

第一个函数返回的是一个生成器对象,它是一个可迭代类型,因此,可以通过for进行访问。

def gen_func():
yield 1
yield 2
yield 3

gen = gen_func()
for val in gen:
print(val)

result:

1

2

3

生成器的原理

Python中函数工作原理

对于编译型语言,函数的调用会维持一个函数调用栈,某个函数执行完成后,它就会被出栈处理,也就是说,函数执行后,它的生命周期就结束了。

对于Python这样的解释型语言,函数的调用也要依赖栈结构,但是,函数对象是存放在堆内存中的,也就意味着,一个函数被调用执行了,它还在那儿。

什么是堆内存和栈内存?

解释器用一个叫做PyEval_EvalFrameEx的C函数来执行Python程序。对于一个Python中的函数,解释器接受一个栈帧(stack frame)对象,并在这个栈帧的上下文中执行Python字节码,完成函数调用。在字节码执行中,如果遇到了要调用其他函数的指令,解释器会创建一个新的栈帧用来执行新调用的函数。

生成器+函数

def gen_func():
yield 1
name = "MetaTian"
yield 2

return "done"
在Python将函数编译为字节码时,如果遇到yield关键字,它就知道这是一个生成器函数,内部会做一个标记。当我们调用这个函数的时候,解释器看到这个标记后就会创建一个生成器,而不是去运行它,后续函数的执行交给生成器控制。

这个生成器内部有两个东西,一是对栈帧的引用,二是函数字节码的引用。栈帧中有一个指针,指向最近执行的那条指令,因为执行到和yield有关的字节码后,函数会停止执行,相当于打了个断点,同时将yield后面的值返回。通过next(),可以让函数继续执行(因为生成器也是迭代器),直到遇到下一个yield。

生成器对象也是分配在堆内存中的,也就是说,只要我们在程序运行的任何地方拿到了这个对象,都可以用它来控制函数的执行。这也是后面携程的一个理论基础。

重构自己的可迭代类型

引言

前面将Language这个类,构建成为了我们自定义的一个可迭代类型。
生成器也是迭代器,通过使用生成器,可以更简洁地达成目的。
from collections.abc import Iterator
def gen_func():
yield "MetaTian"

gen = gen_func()
print(isinstance(gen, Iterator))

result:

True

使用案例

class Language(object):
def init(self, language_list):
self.lans = language_list

def iter(self):
i = 0
try:
while True:
val = self.lans[i]
yield val
i += 1
except IndexError:
return

language = Language(["Python", "C", "Lisp"])
for lan in language:
print(lan)

result:

Python

C

Lisp

总结

这里再来回顾一下前面讲过的内容,使用for遍历的时候,首先会看作用对象是否为一个可迭代类型,如果是,那么会隐式调用iter(),得到一个迭代器对象,有了它,再隐式调用next()来不断获取下一个元素,直到

遇到一个停止迭代的异常。我们也可以通过内置的两个函数iter()和next()来人工干预迭代的过程。

在Language类中,iter()内部加入了一个生成器的逻辑,结合前面的生成器函数,可以知道,遇到yield语句后,会产生一个生成器对象,由它来控制这个函数的后续执行。

因为生成器也是迭代器,所以next()的逻辑对它同样适用,每次 next 都会在iter()函数中的while循环中不断取值,直到抛出一个IndexError,迭代结束,iter()函数结束,for逻辑完成。

生成器读取大文件

引言

有一个数据文件,大小为 10GB
数据只有一行,行中有特殊的分隔符,现在需要剔除分隔符,获得每一个被分隔的元素。
比如,数据文件长这样:
dj134o0kgfdkjfkdjfk'6823sdkfslkfsldkfj'sdkfslfjyerojskfj...
是其中的分隔符
实现过程

def extract(f, sep):
buff = ""

while True:
block = f.read(1024*4)
if not block: # 没读到内容,说明读到尾部了
yield buff # 上次留下来的内容
break

buff += block
while sep in buff:
cut = buff.index(sep) # 定位
yield buff[:cut] # 分隔符前的一个元素
buff = buff[cut + len(sep)] # 跳过分隔符和前面的元素

with open("data.txt") as f:
for line in extract(f, ""):
print(line)
喜欢python + qun:839383765 可以获取Python各类免费最新入门学习资料!

转载于:https://blog.51cto.com/14186420/2349828

python入门系列:迭代器和生成器相关推荐

  1. 十二章 Python入门系列之字典

    系列文章目录 第一章 Python入门系列之介绍 第二章 Python入门系列之PyCharm 第三章 Python入门系列之注释 第四章 Python入门系列之变量 第五章 Python入门系列之输 ...

  2. 【Python入门系列】一个简单的数据分析问题——用Pandas揭秘美国选民的总统喜好

    阿里天池训练营学习笔记 学习链接~AI训练营Python 本文将展示一个实际操作,利用Pandas对美国大选的一些数据进行处理,题目的详细内容请点击此处~[Python入门系列]用Pandas揭秘美国 ...

  3. 第八章 Python入门系列之循环

    系列文章目录 第一章 Python入门系列之介绍 第二章 Python入门系列之PyCharm 第三章 Python入门系列之注释 第四章 Python入门系列之变量 第五章 Python入门系列之输 ...

  4. 视频教程-Python入门-系列游戏开发/太空阻击-Python

    Python入门-系列游戏开发/太空阻击 20年软件项目开发管理经验 工信部人才交流中心特聘专家讲师 日本U-CAN在线教育特聘主任讲师 国家十二·五规划软件工程教材作者(书:清华大学出版社出版) 中 ...

  5. python之路---迭代器和生成器

    阅读目录 楔子 python中的for循环 可迭代协议 迭代器协议 为什么要有for循环 初识生成器 生成器函数 列表推导式和生成器表达式 本章小结 生成器相关的面试题 返回顶部 楔子 假如我现在有一 ...

  6. python之路——迭代器和生成器

    楔子 假如我现在有一个列表l=['a','b','c','d','e'],我想取列表中的内容,有几种方式? 首先,我可以通过索引取值l[0],其次我们是不是还可以用for循环来取值呀? 你有没有仔细思 ...

  7. python之路——迭代器与生成器

    要了解for循环是怎么回事儿,咱们还是要从代码的角度出发. 首先,我们对一个列表进行for循环. for i in [1,2,3,4]: print(i) 上面这段代码肯定是没有问题的,但是我们换一种 ...

  8. python学习-38迭代器和生成器

    迭代器和生成器 ---- 迭代器协议和for循环工作机制 1.迭代器协议:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么引起一个Stoplteration异常,以终止迭代(只能往 ...

  9. 10分钟带你了解python_10分钟Python入门系列教程及学习资源分享

    本期分享笔记内容 归档此前入门教程文章,方便查看 10分钟带你Python入门的特点 简单谈下如何寻找Python学习资源 关于分享Python学习资源的分享问题 本人对于Python学习创建了一个小 ...

  10. [转载] Python 学习笔记 迭代器和生成器

    参考链接: Python中的迭代器函数2(islice(),starmap(),tee()..) 本文链接地址 http://quqiuzhu.com/2016/python-iterator-and ...

最新文章

  1. rails小重构:将图片加入产品Model
  2. 学好机器学习,这里有想要的一切
  3. Java Spring DI之旅
  4. 如何写一个优秀的GitHub项目README文档?
  5. C# 中的委托和事件(详解) ....
  6. C语言解析http请求表单内容
  7. 如何测试数据库表空间不足场景
  8. 根据当前登录域账号 获取AD用户姓名和所在OU目录
  9. 麦克纳姆轮速度分解再分析
  10. js语法、关键保留字、变量、数据类型
  11. 无人驾驶汽车系统入门(十四)——ROS入门与实践(1)
  12. cad画正弦曲线lisp_cadlisp基础教程.pdf
  13. mysql程序设计考试app_MySQL数据库设计与应用知到APP期末考试完整答案
  14. minio权限之IAM policy配置及用户赋权
  15. 高效率的网站打开速度优化方法
  16. 一个IT前辈的JIRA使用心得
  17. j-4 大炮打蚊子 (10 分)关于最后一个测试点出错及本题的具体思路(以作者思路为例)
  18. 203、商城业务-商品详情-环境搭建
  19. JAVA 建造者模式
  20. Android转发短信给QQ机器人

热门文章

  1. keepalived高可用使用案例
  2. 定时自动清除tmp工具tmpwatch
  3. SSLH:让 HTTPS 和 SSH 共享同一个端口
  4. Windows中NTP服务器的搭建和时间同步
  5. Ubuntu16安装OpenStack
  6. 终止正在运行的VBS脚本
  7. bat 字符串截取操作
  8. java线程抢占式执行,Java并发基础(一)-线程基础
  9. mysql 2049_mysql数据库备份与还原,解决40101和ERROR 2049错误
  10. i5功耗最低的cpu_近年最大飞跃 Intel第11代低功耗酷睿处理器官宣:集显2倍、AI乘4...