迭代器(iterator)

Date: 2019-05-23

Author: Sun

为何要引入迭代器?

​ 通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

​ 我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator

1.1 什么迭代器呢?

​ 在Python中如果一个对象有__iter__( )方法和__next__( )方法,则称这个对象是迭代器(Iterator);其中__iter__( )方法是让对象可以用for ... in循环遍历,next( )方法是让对象可以通过next(实例名)访问下一个元素。

​ 注意:这两个方法必须同时具备,才能称之为迭代器。

​ 迭代器是在python2.2中被加入的,它为类序列对象提供了一个类序列的接口。有了迭代器可以迭代一个不是序列的对象,因为他表现出了序列的行为。

​ 迭代器的实质是实现了next()方法的对象,常见的元组、列表、字典都是迭代器。

迭代器中重点关注两种方法:

__iter__方法:返回迭代器自身。可以通过python内建函数iter()调用。

__next__方法:当next方法被调用的时候,迭代器会返回它的下一个值,如果next方法被调用,但迭代器没有只可以返回,就会引发一个StopIteration异常。该方法可以通过 python 内建函数next()调用。

举例

内建函数iter()可以从可迭代对象中获得迭代器。

>>> it = iter([1,2,3])
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration
>>> 

​ 迭代器是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter____next__()(python2中实现next())方法的对象都是迭代器。

__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常,至于它们到底是如何实现的这并不重要。

​ 迭代器就是实现了工厂模式的对象,它在你每次你询问要下一个值的时候给你返回。

1.2 如何创建一个迭代器

要创建一个迭代器有2种方法,其中前两种分别是:

  1. 为容器对象添加 __iter__()__next__() 方法(Python 2.7 中是 next());__iter__() 返回迭代器对象本身 self__next__() 则返回每次调用 next() 或迭代时的元素;
  2. 内置函数 iter() 将可迭代对象转化为迭代器

案例:创建迭代器容器

# Create iterator Object
class Container:def __init__(self, start=0, end=0):self.start = startself.end = enddef __iter__(self):print("I made this iterator!")return selfdef __next__(self):print("Calling __next__ method!")if self.start < self.end:i = self.startself.start += 1return ielse:raise StopIteration()c = Container(0, 5)
for i in c:print(i)

​ 对于迭代器对象,使用for循环遍历整个数组其实是个语法糖,他的内部实现还是通过调用对象的__next__()方法。

​ 创建迭代器对象的好处是当序列长度很大时,可以减少内存消耗,因为每次只需要记录一个值即刻(经常看到人们介绍 Python 2.7 的 range 函数时,建议当长度太大时用 xrange 更快,在 Python 3.5 中已经去除了 xrange 只有一个类似迭代器一样的 range)。

1.3 斐波那契数列应用举例

我们如果采用正常的斐波那契数列求值过程如下:

def fibs(n):if n == 0 or n == 1:return 1else:return fibs(n-1) + fibs(n-2)print([fibs(i) for i in range(10)])

自定义一个迭代器, 实现斐波那契数列

class Fib2(object):def __init__(self, n):self.a = 0self.b = 1self.n = nself.count = 0def __iter__(self):return selfdef next(self):res = self.aself.a, self.b = self.b, self.a + self.bif self.count > self.n:raise StopIterationself.count += 1return resprint(list(Fib2(10)))
其他迭代器案例

(1) 有很多关于迭代器的例子,比如itertools函数返回的都是迭代器对象。

def test_endless_iter():'''生成无限序列:return:'''from itertools import countcounter = count(start=3)print(next(counter))print(next(counter))print(next(counter))  

(2) 从一个有限序列中生成无限序列

def test_cycle_iter():'''从一个有限序列中生成无限序列:return:'''from itertools import cyclecolors = cycle(['red', 'white', 'blue'])print(next(colors))print(next(colors))print(next(colors))print(next(colors))

总结:

​ 对于list、string、tuple、dict等这些容器对象,使用for循环遍历是很方便的。在后台for语句对容器对象调用iter()函数。iter()是python内置函数。
iter()函数会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内的元素。next()也是python内置函数。在没有后续元素时,next()会抛出一个StopIteration异常,通知for语句循环结束。

​ 迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的next方法(Python3中是对象的next方法,Python2中是对象的next()方法)。所以,我们要想构造一个迭代器,就要实现它的next方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现iter方法,而iter方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的iter方法返回自身self即可。

2. 生成器

​ 生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。

生成器作用?

​ 延迟操作。也就是在需要的时候才产生结果,不是立即产生结果。

什么是生成器?

​ 生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器。

​ 生成器是包含yield关键字的函数。本质上来说,关键字yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态。

python中的生成器

​ 要创建一个generator,有很多种方法,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator

​ 举例如下:

# 列表生成式
lis = [x * x for x in range(10)]
print(lis)
# 生成器
generator_ex = (x * x for x in range(10))
print(generator_ex)结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
< generator
object < genexpr > at
0x000002A4CBF9EBA0 >#生成器
generator_ex = (x*x for x in range(10))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))#生成器, 可以直接循环得出结果
generator_ex = (x*x for x in range(10))
for i in generator_ex:print(i)结果:
0
1
4
9
生成器调用顺序

那么,生成器是怎么调用执行的呢?只需要了解下面几条规则即可:

a. 当生成器被调用的时候,函数体的代码不会被执行,而是会返回一个迭代器,其实,生成器函数返回生成器的迭代器。 “生成器的迭代器”这个术语通常被称作”生成器”。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法,其中一个就是next()。如同迭代器一样,我们可以使用next()函数来获取下一个值。需要明白的是,这一切都是在yield内部实现的。

b. 当next()方法第一次被调用的时候,生成器函数才开始执行,执行到yield语句处停止
next()方法的返回值就是yield语句处的参数(yielded value)
当继续调用next()方法的时候,函数将接着上一次停止的yield语句处继续执行,并到下一个yield处停止;如果后面没有yield就抛出StopIteration异常。

c.每调用一次生成器的next()方法,就会执行生成器中的代码,知道遇到一个yield或者return语句。yield语句意味着应该生成一个值(在上面已经解释清楚)。return意味着生成器要停止执行,不在产生任何东西。

d. 生成器的编写方法和函数定义类似,只是在return的地方改为yield。生成器中可以有多个yield。当生成器遇到一个yield时,会暂停运行生成器,返回yield后面的值。当再次调用生成器的时候,会从刚才暂停的地方继续运行,直到下一个yield。生成器自身又构成一个循环器,每次循环使用一个yield返回的值。

注意:

(1)生成器是只能遍历一次的。

(2)生成器是一类特殊的迭代器。

分类:

第一类:生成器函数:

生成器函数:也是用def定义的,利用关键字yield一次性返回一个结果,阻塞,重新开始

​ 还是使用 def 定义函数,但是,使用yield而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。

如下案例加以说明:

def Fib3(max):n, a, b = 0, 0, 1while n < max:yield ba, b = b, a + bn = n + 1return '亲!没有数据了...'print("@@@@@@@@@@@@@222")
# 调用方法,生成出10个数来
f=Fib(10)
# 使用一个循环捕获最后return 返回的值,保存在异常StopIteration的value中
while  True:try:x=next(f)print("f:",x)except StopIteration as e:print("生成器最后的返回值是:",e.value)break

第二类:生成器表达式:

​ 类似于列表推导,只不过是把一对大括号[]变换为一对小括号()。但是,生成器表达式是按需产生一个生成器结果对象,要想拿到每一个元素,就需要循环遍历。

如下案例加以说明:

# 一个列表
xiaoke=[2,3,4,5]
# 生成器generator,类似于list,但是是把[]改为()
gen=(a for a  in xiaoke)
for  i  in gen:print(i)
#结果是:
2
3
4
5

​ 为什么要使用生成器?因为效率。使用生成器表达式取代列表推导式可以同时节省 cpu 和 内存(RAM)。如果你构造一个列表(list)的目的仅仅是传递给别的函数, 比如 传递给tuple()或者set(), 那就用生成器表达式替代吧!

iter

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:

class Fib(object):def __init__(self):self.a, self.b = 0, 1 # 初始化两个计数器a,bdef __iter__(self):return self # 实例本身就是迭代对象,故返回自己def next(self):self.a, self.b = self.b, self.a + self.b # 计算下一个值if self.a > 100000: # 退出循环的条件raise StopIteration();return self.a # 返回下一个值

现在,试试把Fib实例作用于for循环:

>>> for n in Fib():
...     print n
...
1
1
2
3
5
...
46368
75025

getitem

要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:

class Fib(object):def __getitem__(self, n):a, b = 1, 1for x in range(n):a, b = b, a + breturn a

现在,就可以按下标访问数列的任意一项了:

>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101

但是list有个神奇的切片方法:

>>> range(100)[5:10]
[5, 6, 7, 8, 9]

对于Fib却报错。原因是__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:

class Fib(object):def __getitem__(self, n):if isinstance(n, int):a, b = 1, 1for x in range(n):a, b = b, a + breturn aif isinstance(n, slice):start = n.startstop = n.stopa, b = 1, 1L = []for x in range(stop):if x >= start:L.append(a)a, b = b, a + breturn L

现在试试Fib的切片:

>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

但是没有对step参数作处理:

>>> f[:10:2]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

也没有对负数作处理,所以,要正确实现一个__getitem__()还是有很多工作要做的。

文件是可迭代对象,也是迭代器

f = open('housing.csv')
from collections import Iterator
from collections import Iterableprint(isinstance(f, Iterator))     #判断是不是迭代器
print(isinstance(f, Iterable))     #判断是不是可迭代对象True
True

小结:

  • 凡是可作用于for循环的对象都是Iterable类型;
  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
  • 集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python3的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:pass

实际上完全等价于

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:try:# 获得下一个值:x = next(it)except StopIteration:# 遇到StopIteration就退出循环break

yield的总结

  (1)通常的for..in...循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。他可以是a = [1,2,3],也可以是a = [x*x for x in range(3)]。

它的缺点也很明显,就是所有数据都在内存里面,如果有海量的数据,将会非常耗内存。

  (2)生成器是可以迭代的,但是只可以读取它一次。因为用的时候才生成,比如a = (x*x for x in range(3))。!!!!注意这里是小括号而不是方括号。

  (3)生成器(generator)能够迭代的关键是他有next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。

  (4)带有yield的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代

  (5)yield是一个类似return 的关键字,迭代一次遇到yield的时候就返回yield后面或者右面的值。而且下一次迭代的时候,从上一次迭代遇到的yield后面的代码开始执行

  (6)yield就是return返回的一个值,并且记住这个返回的位置。下一次迭代就从这个位置开始。

  (7)带有yield的函数不仅仅是只用于for循环,而且可用于某个函数的参数,只要这个函数的参数也允许迭代参数。

  (8)send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式值。

  (9)send()和next()都有返回值,他们的返回值是当前迭代遇到的yield的时候,yield后面表达式的值,其实就是当前迭代yield后面的参数。

  (10)第一次调用时候必须先next()或send(),否则会报错,send后之所以为None是因为这时候没有上一个yield,所以也可以认为next()等同于send(None)

转载于:https://www.cnblogs.com/sunBinary/p/10940974.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/448725.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初识python之函数基础

课堂笔记&#xff1a; 1、什么是函数&#xff1f;函数相当于工具&#xff0c;需要事先准备好&#xff0c;在需要用时再使用。2、如何使用函数&#xff1f;函数必须先定义、后调用。3、函数的语法:# def 函数名(参数1,参数2...):# """# 注释# 函数的说明# 水…

java 的几种对象 (PO,VO,DAO,BO,POJO) 解释

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 一、PO:persistant object 持久对象,可以看成是与数据库中的表相映射的java对象。最简单的PO就是对应数据库中某个表中的一条记录&#x…

【随想】每日两题Day.22

题目&#xff1a;102. 二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[…

帮助子女成功的十大路径

美国全国家长协会(National PTA)建议指出&#xff1a;作为家长您对你子女的成功起着非常重要的影响作用&#xff0c;并举出帮助子女成功的十种路径。 1、与子女沟通 如果我们尽早地与子女沟通&#xff0c;提供给他们信息与行为准则&#xff0c;获得子女的信任&#xff0c;在…

shell关闭指定进程

例如要关闭jupyter-notebook这个进程&#xff1a; ps -ef | grep jupyter-notebook | grep -v grep | cut -c 9-15 | xargs kill -9 说明&#xff1a;管道符“|”用来隔开两个命令&#xff0c;管道符左边命令的输出会作为管道符右边命令的输入。 “ps -ef” 查看所有进程  …

垃圾回收算法与垃圾回收器

Java与C等语言最大的技术区别&#xff1a;自动化的垃圾回收机制&#xff08;GC&#xff09; 为什么要了解GC和内存分配策略 1、面试需要 2、GC对应用的性能是有影响的&#xff1b; 3、写代码有好处 栈&#xff1a;栈中的生命周期是跟随线程&#xff0c;所以一般不需要关注 堆&a…

提高孩子睡眠质量 学业事半功倍

睡眠如同大脑的食物。在睡眠期间&#xff0c;许多重要的身体机能静静地发生著作用。省略睡眠是有害的&#xff0c;如果一个严重缺觉的人开着车&#xff0c;他会脸色苍白、喜怒无常、反应迟钝&#xff0c;可能是致命的危险。缺少睡眠让青少年很难与人相处&#xff0c;学业表现不…

实体类(VO,DO,DTO)的划分

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 从领域建模中的实体划分、项目中的实际应用情况两个角度&#xff0c;对这几个概念进行简析。 得出的主要结论是&#xff1a;在项目应用…

IIS新建站点服务器,localhost能登录但是IP访问登录不了。

IIS服务器新建站点之后&#xff0c;浏览页面&#xff0c;服务器本地是可以登录&#xff0c;但是localhost换成IP就无法访问。其他站点IP却可以访问。 1.如果浏览直接失败&#xff0c;说明端口号需要更换。 2.如果出现IP不能访问&#xff0c;localhost能访问&#xff0c;需要在高…

eclipse问题_Alt+/不给提示,只补充代码问题的解决方案

今天用eclipse敲代码的时候遇到的问题 我还以为是冲突什么的 还重新装了软件 最后才发现原来是快捷键设置的问题 解决方案&#xff1a; 1&#xff1a;打开菜单window→Preferences&#xff0c;然后在窗口的左侧树选择General->Keys 2&#xff1a;在下图中的5框的地方输入“w…

领域驱动设计:浅析 VO、DTO、DO、PO 概念、区别、用处

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 本篇文章主要讨论一下我们经常会用到的一些对象&#xff1a;VO、DTO、DO和PO。 由于不同的项目和开发人员有不同的命名习惯&#xff0c…

动脑的生活教育

心理学家华生曾经说过&#xff1a;“如果给我一打孩子&#xff0c;我可以把他们变成律师、医师、科学家&#xff0c;或是强盗、土匪。”华生认为&#xff0c;教育孩子就如同马戏团的驯兽师训练野兽一样&#xff0c;是“刺激”与“反应”的联结&#xff0c;不需要任何的“思考”…

前端知识点回顾之重点篇——CORS

CORS&#xff08;cross origin resource sharing&#xff09;跨域资源共享 来源&#xff1a;http://www.ruanyifeng.com/blog/2016/04/cors.html 它允许浏览器向跨源服务器&#xff0c;发出XMLHttpRequest请求&#xff0c;从而克服了AJAX只能同源使用的限制。 简介 CORS需要浏览…

案例:隐秘而低调的内存泄露(OOM)

内存泄露测试的整个过程如下&#xff1a;在手机里启动被测APP并打开DDMS。在DDMS中选中【com.example.android.hcgallery】之后单击按钮【show heap updates】&#xff0c;然后切换到标签页【VM Heap】&#xff0c;再单击按钮【Cause GC】。不断操作APP&#xff0c;并观察Heap。…

员工价值——如何体现自己价值,如何被自己的领导认可

到公司工作快三年了&#xff0c;比我后来的同事陆续得到了升职的机会&#xff0c;我却原地不动&#xff0c;心里颇不是滋味。终于有一天&#xff0c;冒着被解聘的危险&#xff0c;我找到老板理论。 “老板&#xff0c;我有过迟到、早退或乱章违纪的现象吗&#xff1f;”我问。 …

java: PO,VO,TO,BO,DAO,POJO 解释

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 O/R Mapping 是 Object Relational Mapping&#xff08;对象关系映射&#xff09;的缩写。通俗点讲&#xff0c;就是将对象与关系数据库绑…

[译]JavaScript 究竟是如何工作的?(第一部分)

原文地址&#xff1a;How Does JavaScript Really Work? (Part 1)原文作者&#xff1a;Priyesh Patel如果你是一个 JS 开发者或者是正在学习这门语言的学生&#xff0c;很大概率上你会遇到双字母词"V8"。在这篇文章中&#xff0c;我将会为你简述不同的 JS 引擎并深入…

vue实战(9):总结二

整理前一段所做的工作内容 0.其它 vue实战&#xff08;1&#xff09;&#xff1a;准备与资料整理vue实战&#xff08;2&#xff09;&#xff1a;初始化项目、搭建底部导航路由vue实战&#xff08;3&#xff09;&#xff1a;底部导航显示、搭建各模块静态页面、添加登录页页面与…

一名IT从业者的英语口语能力成长路径

这篇文章是我最近十天口语系列文章的合辑&#xff0c;文章比较长&#xff0c;一万五千余字。但是系统化地归纳了自己十多年的英语尤其是口语方面的学习经历与总结思考。我不是个纯粹的英语专业学生&#xff0c;我甚至不是任何英语相关专业的学生&#xff0c;但是我和英语却有着…

解决:SpringBoot 错误:Caused by: org.yaml.snakeyaml.scanner.ScannerException

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 错误: Caused by: org.yaml.snakeyaml.scanner.ScannerException: while scanning for the next tokenfound character that cannot s…