迭代器生成器是Python很重要的进阶语法。虽然在一开始的学习中,不理解它们并不影响你写代码。但到一定阶段之后,如果没有掌握其原理,你可能无法彻底理解代码的运行逻辑。今天我们就给大家分享一篇关于迭代器和生成器的深度解读。

迭代器与可迭代对象

概念

迭代器:是访问数据集合内元素的一种方式,一般用来遍历数据,但是他不能像列表一样使用下标来获取数据,也就是说迭代器是不能返回的。

  1. Iterator:迭代器对象,必须要实现next魔法函数

  2. Iterable:可迭代对象,继承Iterator,必须要实现iter魔法函数

比如:

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

返回结果:

False
True

在Pycharm中使用alt+b进去list的源码中可以看到,在list类中有iter魔法函数,也就是说只要实现了iter魔法函数,那么这个对象就是可迭代对象。

上面的例子中a是一个列表,也是一个可迭代对象,那么如何才能让这个a变成迭代器呢?使用iter()即可。

from collections import Iterable,Iterator
a = [1,2,3]
a = iter(a)
print(isinstance(a,Iterator))
print(isinstance(a,Iterable))
print(next(a))
print('----')
for x in a:print(x)

返回结果:

True
True
1
----
2
3

可以看到现在a是可迭代对象又是一个迭代器,说明列表a中有iter方法,该方法返回的是迭代器,这个时候使用next就可以获取a的下一个值,但是要记住迭代器中的数值只能被获取一次。

梳理迭代器(Iterator)与可迭代对象(Iterable)的区别:

  1. 可迭代对象:继承迭代器对象,可以用for循环(说明实现了iter方法)

  2. 迭代器对象:可以用next获取下一个值(说明实现了next方法),但是每个值只能获取一次,单纯的迭代器没有实现iter魔法函数,所以不能使用for循环

  3. 只要可以用作for循环的都是可迭代对象

  4. 只要可以用next()函数的都是迭代器对象

  5. 列表,字典,字符串是可迭代对象但是不是迭代器对象,如果想变成迭代器对象可以使用iter()进行转换

  6. Python的for循环本质上是使用next()进行不断调用,for循环的是可迭代对象,可迭代对象中有iter魔法函数,可迭代对象继承迭代器对象,迭代器对象中有next魔法函数

  7. 一般由可迭代对象变迭代器对象

可迭代对象

可迭代对象每次使用for循环一个数组的时候,本质上会从类中尝试调用iter魔法函数,如果类中有iter魔法函数的话,会优先调用iter魔法函数,当然这里切记iter方法必须要返回一个可以迭代的对象,不然就会报错。

如果没有定义iter魔法函数的话,会创建一个默认的迭代器,该迭代器调用getitem魔法函数,如果你没有定义iter和getitem两个魔法函数的话,该类型就不是可迭代对象,就会报错。

比如:

class s:def __init__(self,x):self.x = xdef __iter__(self):return iter(self.x)# 这里必须要返回一个可以迭代的对象# def __getitem__(self, item):#     return self.x[item]
# iter和getitem其中必须要实现一个
a = s('123')
# 这里的a就是可迭代对象
# 这里不能调用next(a)方法,因为没有定义
for x in a:print(x)

这里把注释符去掉返回结果也是一样的,返回结果:

1
2
3

迭代器对象

一开始提起,iter搭配Iterable做可迭代对象,next搭配Iterator做迭代器。next()接受一个迭代器对象,作用是获取迭代器对象的下一个值,迭代器是用来做迭代的,只会在需要的时候产生数据。

和可迭代对象不同,可迭代对象一开始是把所有的列表放在一个变量中,然后用getitem方法不断的返回数值,getitem中的item就是索引值。

但是next方法并没有索引值,所以需要自己维护一个索引值,方便获取下一个变量的位置。

class s:def __init__(self,x):self.x = x# 获取传入的对象self.index = 0# 维护索引值def __next__(self):try:result = self.x[self.index]# 获取传入对象的值except IndexError:# 如果索引值错误raise StopIteration# 抛出停止迭代self.index += 1# 索引值+1,用来获取传入对象的下一个值return result# 返回传入对象的值a = s([1,2,3])
print(next(a))
print('----------')
for x in a:
# 类中并没有iter或者getitem魔法函数,不能用for循环,会报错print(x)

返回结果:

Traceback (most recent call last):
1
----------File "C:/CODE/Python进阶知识/迭代协议/迭代器.py", line 34, in <module>for x in a:
TypeError: 's' object is not iterable

上面一个就是完整的迭代器对象,他是根据自身的索引值来获取传入对象的下一个值,并不是像可迭代对象直接把传入对象读取到内存中,所以对于一些很大的文件读取的时候,可以一行一行的读取内容,而不是把文件的所有内容读取到内存中。

这个类是迭代器对象,那么如何才能让他能够使用for循环呢?那就让他变成可迭代对象,只需要在类中加上iter魔法函数即可。

class s:def __init__(self,x):self.x = x# 获取传入的对象self.index = 0# 维护索引值def __next__(self):try:result = self.x[self.index]# 获取传入对象的值except IndexError:# 如果索引值错误raise StopIteration# 抛出停止迭代self.index += 1# 索引值+1,用来获取传入对象的下一个值return result# 返回传入对象的值def __iter__(self):return self
a = s([1,2,3])
print(next(a))
print('----------')
for x in a:print(x)

返回结果:

1
----------
2
3

可以看到这个时候运行成功,但是这个对象还是属于迭代器对象,因为在next获取下一个值会报错。

知识整理

根据上面的代码提示,得到规律:

  1. iter让类变成可迭代对象,next让类变成迭代器(要维护索引值)。

  2. 可迭代对象可以用for循环,迭代器可以用next获取下一个值。

  3. 迭代器如果想要变成可迭代对象用for循环,就要在迭代器内部加上iter魔法函数

  4. 可迭代对象如果想要能用next魔法函数,使用自身类中的iter()方法即可变成迭代器对象

代码演示:

class s:def __init__(self,x):self.x = xself.index = 0def __next__(self):try:result = self.x[self.index]except IndexError:raise StopIterationself.index += 1return resultclass b:def __init__(self,x):self.x = xdef __iter__(self):return s(self.x)
a = b([1,2,3])for x in a:print(x)

返回结果:

1
2
3

这个时候是不能再用next方法了,应为类b是一个可迭代对象,并非迭代器,这个时候不能用next方法,但是可以让类b继承类s,这样就能用next()方法获取下一个值,但是你的类b中要存在索引值,不然会报错,如下代码:

class s:def __init__(self,x):self.x = x# 获取传入的对象self.index = 0# 维护索引值def __next__(self):try:result = self.x[self.index]# 获取传入对象的值except IndexError:# 如果索引值错误raise StopIteration# 抛出停止迭代self.index += 1# 索引值+1,用来获取传入对象的下一个值return result# 返回传入对象的值# def __iter__(self):#     return self
class b(s):def __init__(self,x):self.x = xself.index = 0def __iter__(self):return s(self.x)
a = b([1,2,3])print(next(a))
print(next(a))

返回结果:

1
2

可以这么做,但是没必要,因为这样违反了设计原则。

迭代器的设计模式

迭代器模式:提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部
表示。

迭代器的设计模式是一种经典的设计模式,根据迭代器的特性(根据索引值读取下一个内容,不一次性读取大量数据到内存)不建议将next和iter都写在一个类中去实现。

新建一个迭代器,用迭代器维护索引值,返回根据索引值获取对象的数值,新建另一个可迭代对象,使用iter方法方便的循环迭代器的返回值。

生成器

生成器:函数中只要有yield,这个函数就会变成生成器。每次运行到yield的时候,函数会暂停,并且保存当前的运行状态,返回返回当前的数值,并在下一次执行next方法的时候,又从当前位置继续往下走。概念

简单用法

举个例子:

def gen():yield 1# 返回一个对象,这个对象的值是1
def ret():return 1# 返回一个数字1
g = gen()
r = ret()
print(g,r)
print(next(g))

返回结果:

<generator object gen at 0x000001487FDA2D58> 1
1

可以看到return是直接返回数值1,yield是返回的一个生成器对象,这个对象的值是1,使用next(g)或者for x in g:print x 都是可以获取到他的内容的,这个对象是在python编译字节码的时候就产生。

def gen():yield 1yield 11yield 111yield 1111yield 11111yield 111111# 返回一个对象,这个对象内的值是1和11,111...
def ret():return 1return 3# 第二个return是无效的
g = gen()
r = ret()
print(g,r)
print(next(g))
for x in g:print(x)

返回结果:

<generator object gen at 0x000002885FE32D58> 1
1
11
111
1111
11111
111111

就像迭代器的特性一样,获取过一遍的值是没法再获取一次的,并且不是那种一次把所有的结果求出放在内存或者说不是一次性读取所有的内容放在内存中。

梳理特性:

  1. 使用yield的函数都是生成器函数

  2. 可以使用for循环获取值,也可以使用next获取生成器函数的值

原理

函数工作原理:函数的调用满足“后进先出”的原则,也就是说,最后被调用的函数应该第一个返回,函数的递归调用就是一个经典的例子。显然,内存中以“后进先出”方式处理数据的栈段是最适合用于实现函数调用的载体,在编译型程序语言中,函数被调用后,函数的参数,返回地址,寄存器值等数据会被压入栈,待函数体执行完毕,将上述数据弹出栈。这也意味着,一个被调用的函数一旦执行完毕,它的生命周期就结束了。

python解释器运行的时候,会用C语言当中的PyEval_EvalFramEx函数创建一个栈帧,所有的栈帧都是分配再堆内存上,如果不主动释放就会一直在里面。

Python 的堆栈帧是分配在堆内存中的,理解这一点非常重要!Python 解释器是个普通的 C 程序,所以它的堆栈帧就是普通的堆栈。但是它操作的 Python 堆栈帧是在堆上的。除了其他惊喜之外,这意味着 Python 的堆栈帧可以在它的调用之外存活。(FIXME: 可以在它调用结束后存活),这个就是生成器的核心原理实现。

Python脚本都会被python.exe编译成字节码的形式,然后python.exe再执行这些字节码,使用dis即可查看函数对象的字节码对象。

import dis
# 查看函数程序字节码
a = 'langzi'
print(dis.dis(a))
print('-'*20)
def sb(admin):print(admin)
print(dis.dis(sb))

返回结果:

  1           0 LOAD_NAME                0 (lzngzi)
# 加载名字 为langzi2 RETURN_VALUE
# 返回值
None
--------------------15           0 LOAD_GLOBAL              0 (print)
# 加载一个print函数2 LOAD_FAST                0 (admin)
# 加载传递参数为admin4 CALL_FUNCTION            1
# 调用这个函数6 POP_TOP
# 从栈的顶端把元素移除出来8 LOAD_CONST               0 (None)
# 因为该函数没有返回任何值,所以加载的值是none10 RETURN_VALUE
# 最后把load_const的值返回(个人理解)
None

代码函数运行的时候,python将代码编译成字节码,当函数存在yield的时候,python会将这个函数标记成生成器,当调用这个函数的时候,会返回生成器对象,调用这个生成器对象后C语言中写的函数会记录上次代码执行到的位置和变量。

再C语言中的PyGenObject中有两个值,gi_frame(存储上次代码执行到的位置f_lasti的上次代码执行到的变量f_locals),gi_code(存储代码),使用dis也可以获取到上次代码执行的位置和值。

举个例子:

import dis
def gen():yield 1yield 2return 666g = gen()
# g是生成器对象
print(dis.dis(g))
print('*'*10)
print(g.gi_frame.f_lasti)
# 这里还没有执行,返回的位置是-1
print(g.gi_frame.f_locals)
# 这里还没有执行,返回的对象是{}
next(g)
print('*'*10)
print(g.gi_frame.f_lasti)
print(g.gi_frame.f_locals)

返回结果:

 11           0 LOAD_CONST               1 (1)
# 加载值为12 YIELD_VALUE4 POP_TOP12           6 LOAD_CONST               2 (2)8 YIELD_VALUE10 POP_TOP13          12 LOAD_CONST               3 (666)14 RETURN_VALUE
None
**********
-1
# 因为还没有执行,所以获取的行数为 -1
{}
**********
2
# 这里开始执行了第一次,获取的行数是2,2对应2 YIELD_VALUE就是前面加载的数值1
{}
# g.gi_frame.f_locals 是局部变量,你都没定义那么获取的结果自然是{},你只需在代码中加上user='admin',这里的{}就会改变。

生成器可以在任何时候被任何函数恢复执行,因为它的栈帧实际上不在栈上而是在堆上。生成器在调用调用层次结构中的位置不是固定的,也不需要遵循常规函数执行时遵循的先进后出顺序。因为这些特性,生成器不仅能用于生成可迭代对象,还可以用于实现多任务协作。

就是说只要拿到了这个生成器对象,就能对这个生成器对象进行控制,比如继续执行暂停等待,这个就是协程能够执行的理论原理。

应用场景

读取文件,使用open('xxx').read(2019)//打开一个文件,每次读取2019个偏移量。文件a.txt是一行文字,但是特别长,这一行文字根据|符号分开,如何读取?

写入文件代码:

# -*- coding:utf-8 -*-
import random
import threading
import string
import time
t1 = time.time()
def write(x):with open('a.txt','a+')as a:a.write(x + '||')def run():for x in range(10000000):strs = str(random.randint(1000,2000)) +random.choice(string.ascii_letters)*10write(strs)
for x in range(10):t = threading.Thread(target=run)t.start()
t2 = time.time()
print(t2 - t1)

读取文件代码:

# -*- coding:utf-8 -*-
def readbooks(f, newline):# f为传入的文件名,newline为分隔符buf = ""# 缓存,处理已经读出来的数据量while 1:while newline in buf:# 缓存中的数据是否存在分隔符pos = buf.index(newline)# 如果存在就找到字符的位置,比如0或者1或者2yield buf[:pos]# 暂停函数,返回缓存中的从头到字符的位置buf = buf[pos + len(newline):]# 缓存变成了,字符的位置到末尾chunk = f.read(2010 * 10)# 读取2010*10的字符if not chunk:# 已经读取到了文件结尾yield bufbreakbuf += chunk# 加到缓存with open('a.txt','r')as f:for line in readbooks(f,'||'):print(line)

深入浅出地解读Python迭代器和生成器相关推荐

  1. 简单介绍python迭代器和生成器

    这篇文章主要介绍了Python中的迭代器和生成器,涉及到Python中很多重要的特性,需要的朋友可以参考下,希望能够给你带来帮助 python迭代器和生成器 1.迭代器 这里用字典示例 while T ...

  2. python生成器 图片分类_python批量处理图片图片Python迭代器和生成器介绍

    Python迭代器和生成器介绍迭代器 迭代器是一个实现了迭代器协议的对象,Python中的迭代器协议就是有next方法的对象会前进到下一结果,而在一系列结果的末尾是,则会引发StopIteration ...

  3. python迭代器与生成器答案_史上最全 Python 迭代器与生成器

    原标题:史上最全 Python 迭代器与生成器 作者:浪子燕青 链接:http://www.langzi.fun/迭代器与生成器.html 迭代器与可迭代对象 概念 迭代器:是访问数据集合内元素的一种 ...

  4. 五分钟学会python函数_五分钟带你搞懂python 迭代器与生成器

    前言 大家周末好,今天给大家带来的是Python当中生成器和迭代器的使用. 我当初第一次学到迭代器和生成器的时候,并没有太在意,只是觉得这是一种新的获取数据的方法.对于获取数据的方法而言,我们会一种就 ...

  5. Python迭代器和生成器详解(包括yield详解)

    文章目录 一.迭代器 1. 可迭代对象(Iterable) 2. 迭代器对象(Iterator) 3. for 循环原理 4. 迭代器的优缺点 二.生成器 1. yield 原理 2. yield 和 ...

  6. 全面理解Python迭代器和生成器

    | 在Python中,很多对象都是可以通过for语句来直接遍历的,例如list.string.dict等等,这些对象都可以被称为可迭代对象.至于说哪些对象是可以被迭代访问的,就要了解一下迭代器相关的知 ...

  7. python 列表生成器放while_史上最全 Python 迭代器与生成器

    原标题:史上最全 Python 迭代器与生成器 转自:浪子燕青 http://www.langzi.fun/迭代器与生成器.html 概念 迭代器:是访问数据集合内元素的一种方式,一般用来遍历数据,但 ...

  8. python迭代器与生成器实际用途_python迭代器和生成器

    python迭代器和生成器 一.迭代器 1.什么是迭代器 说迭代器之前有个相关的名词需要介绍: 可迭代对象:只要定义了iter()方法,我们就说该对象是可迭代对象,并且可迭代对象能提供迭代器. 在Py ...

  9. python迭代器和生成器_python中迭代器和生成器。

    前言:很多python教程中,对python的解释不容易理解,本文记录自己的理解和体会,是对迭代器和生成器的初步理解. 迭代器: 迭代器的实质是实现了next()方法的对象,常见的元组.列表.字典都是 ...

最新文章

  1. 【建站系列教程】5、谈一谈网站的静态化
  2. java结构化语言,如何让Java代码流畅和结构化
  3. 多线程并发下的单例模式
  4. boost::geometry::geometry_id用法的测试程序
  5. java读取文件跳过_在Java中读取文本文件-为什么跳过行?
  6. idea install 失败_idea maven install 卡住,无报错排查。
  7. Linux 截取线上日志
  8. Advanced Bash Sell Scripting学习笔记1
  9. php radio用法,JavaScript_JQuery radio(单选按钮)操作方法汇总,随着Jquery的作用越来越大,使 - phpStudy...
  10. 性能测试的那些事儿!
  11. 大厂首选,为什么 SRE 比传统运维更抢手?
  12. 计算机组成原理第五章考试题,计算机组成原理第五章部分课后题答案(唐朔飞版).doc...
  13. Microsoft.mshtml.dll 添加引用及类型选择错误问题解决办法
  14. 【转】DVI转HDMI没声音怎么办
  15. 2021年与 Linux 有关的几件大事
  16. Word转为PDF,并在线预览。
  17. matlab 求傅里叶级数,MATLAB傅里叶级数.docx
  18. 关于Eureka的自我保护模式
  19. Angel 实现FFM 一、对于Angel 和分布式机器学习的简单了解
  20. 计算机组成原理练习题(第6章 计算机的运算方法(中))

热门文章

  1. pycharm ctrl+shift+F 全局搜索失灵怎么办?(输入法关闭简繁体输入切换)
  2. python中缩进规则的例外
  3. c++ 结构体构造函数使用总结 附一道经典模拟题
  4. html2image api,图像标签_图像识别 Image_API参考_API_华为云
  5. 不装客户端连接mysql_C#不安装oracle客户端,如何连接到oracle数据库
  6. spring cloud 2.3.x 注册中心eureka 配置
  7. 映射Mapper.xml文件的几种方式
  8. gradle jar 修改 output 路径_Java 添加、修改、读取PDF书签
  9. java数独最快解_[分享]数独的JAVA解法
  10. java程序设计_80后程序员,带你深入理解Java基本的程序设计结构,不来你别后悔...