初学者在日常提升Python基本功的时候,可能会被Python的迭代器和生成器搞晕,之前在学习和使用时,本来for in 循环体和enumerate函数用的飞起,觉得自己已经彻底了解了Python的迭代特性,但接触了迭代器和生成器后,突然感觉懵逼,大概率会被可迭代、迭代器、生成器等概念搞的不知所向,本文就是结合日常项目应用,对Python的迭代概念进行系统性的全面解析,包括其底层实现原理,还有一些常见的应用,希望能帮助更多人,同时也算作给自己梳理思路。

一、迭代概述

1.1 基础概念

迭代属性是Python一大特性,也才允许我们通过for  in 循环体遍历比如列表、字典等集合类型数据类型内的数据,或者用in成员函数判断某元素是否在某数据内存在、使用列表解析式等,让代码变得简洁明晰,如果想深入理解Python这一大特性,其实还需要深入了解迭代器和生成器的概念。

以下先整体介绍可迭代、迭代器、生成器的概念和相互之间的关系

  1. 可迭代:指实现了Python迭代协议,可以通过for in 循环体遍历的对象,比如list、dict等内置数据类型、迭代器、生成器
  2. 迭代器:指可以记住自己遍历位置的对象,直观体现便是可以使用next()函数返回值,迭代器只能往前,不能往后,当遍历完毕后,next(iteror)会抛出一个StopIteration异常
  3. 生成器:指使用yield的函数,生成器也是只能往前,不能往后,当遍历完毕后,next(iteror)会抛出一个StopIteration异常
  4. 三个概念的包含关系:可迭代>迭代器>生成器
  5. 迭代器和生成器,均可以通过next(obj)的方式不断返回下一个值
  6. 可迭代的对象(包括生成器),均可以通过iter(obj),转化为迭代器

1.2 判断对象是否可迭代方法

python也提供了判断是否可迭代的方法,即isinstance,代码如下

from collections import Iterable
from collections import Iterator
list1=[1,2,3]
print(isinstance(list1,Iterable)) #返回True
print(isinstance(list1,Iterator)) #返回False

1.3 迭代器和生成器的比较

  1. 迭代器是个类,且需要实现__iter__和__next__魔法函数,语法相对来说较为冗余
  2. 生成器是个使用yield的函数,相较而言,代码会更加少
  3. 在同一代码内,生成器只能遍历一次

1.4 for in循环运作原理以及其与可迭代关系

1.4.1 for in 循环运作原理

#1、只实现__getitem__
class A:def __init__(self):self.data=[1,2,3]def __getitem__(self,index):return self.data[index]
a=A()
for i in a:print(i)
#输出为 1、2、3#2、实现__getitem__和__iter__
class A:def __init__(self):self.data=[1,2,3]self.data1=[4,5,6]def __iter__(self):return iter(self.data1)    def __getitem__(self,index):return self.data[index]a=A()
for i in a:print(i)
#输出为 4、5、6
  1. 如以上代码所示,如果只是实现__getitem__,for in 循环体会自动调用__getitem__函数,并自动对Index从0开始自增,并将对应索引位置的值赋值给 i,直到引发IndexError错误
  2. 如果还实现了__iter__,则会忽略__getitem__,只调用__iter__,并对__iter__返回的迭代器进行成员遍历,并自动将遍历的成员逐次赋值给 i,直到引发StopIteration

1.4.2 其与可迭代关系

  1. 可迭代的对象一定可以支持for in 循环体,以及其他迭代环境,比如in成员判断、列表解析、map和reduce函数等
  2. 支持for in 循环体及迭代环境的,不一定可迭代,如1.4.1中所示,实现了__getitem__的对象

1.5 python迭代环境及对应实现介绍

在Python中,迭代环境到处可见,主要有:

  1. for in 循环
  2. in成员判断运算 ( x in y)
  3. 列表推导式[x for x in range(10]
  4. map和reduce函数(map(func,a))
  5. 列表及元组赋值语句(比如a,b=[1,2])
  6. next()

以上迭代环境,都依赖于迭代协议,对应调用的魔法函数也会有不同,以下罗列下不同的迭代环境,对应的魔法函数,后续自定义类时,如果需要这个类实例对象支持相应的迭代环境,则需要实现对应的魔法函数

迭代环境 支持该迭代环境的实现方式
 for in 循环

1、可只是实现__iter__魔法函数,该魔法函数返回一个迭代器对象

2、可只是实现__getitem__(self,index)魔法函数,该魔法函数每次循环均会对index从0自增

3、如果两个都实现了,则会调用__iter__

in 成员判断

1、可只是实现__contains__(self,value)魔法函数,in 运算符,会自动将该函数返回值转化为对应布尔值

2、可只是实现__iter__魔法函数

3、可只是实现__getitem__(self,index)魔法函数

4、一般判断先使用__contains__,再用__iter__,再用__getitem__,为标准起见,最好用__contains__单独支持 in 成员判断

列表推导式

与for in 一致

map和reduce函数 与for in 一致
列表及元组赋值语句

与for in 一致

  1. 如果实现了__iter__,则可以支持Python所有迭代环境
  2. 如果实现了__getitem__,也可以支持Python所有迭代环境,但是优先级和灵活度没有__iter__高,并且此时该类的对象,并不是可迭代的
  3. __contains__魔法函数,只对in 生效,所以如果要单独定义专有的in 运算,则最好只是实现__contains__即可
  4. 如果想支持next(a)函数调用,则必须实现__next__魔法函数
  5. 预估后续python会对迭代这块进行优化,因为现在其实整体感觉蛮混乱,如果强制可迭代,必须通过__iter__+__next__实现,反而会更加明晰些
  6. 建议读者:
    1. 实现迭代协议:就优先重载实现__iter__和__next__
    2. 支持in成员运算符:就优先重载实现__contains__(self,value),且只用该魔法函数支持in运算,不去支持迭代协议
    3. 支持索引和切片运算:就优先重载实现__getitem__(self,index),且只用该魔法函数支持索引和切片,不去支持迭代协议

二、可迭代对象

下面展开讲解如何创建一个可迭代对象及其实现原理

2.1 可迭代对象创建方式

下面演示如何创建一个可迭代对象,核心点:

  1. 关键是在定义类的时候,需要实现__iter__魔法函数,该函数返回一个迭代器即可
  2. 实现了__iter__,也即实现了Python的迭代协议
class Myiter:def __init__(self):self.a=1def __iter__(self):a=[1,2,3,4]return iter(a)
#此种实现的方式,不是一个迭代器,但是可迭代,即可通过for in 循环体进行遍历
it=Myiter()
for i in it:print(it)

2.2 可迭代对象原理讲解

  1. 上面的代码,只是实现了__iter__魔法函数,但是已经可以在for in 循环体内进行遍历
  2. 此时,因为没有实现__next__模范函数,所以只是可迭代对象,但并不是迭代器
  3. 比如list数据类型,是可迭代对象,但并不是迭代器,可以观察list数据类型魔法函数,使用dir(list),其输出中有__iter__魔法函数,但并没有__next__魔法函数

三、迭代器

如一中所属,一个迭代器就是可以通过next()不断返回下一个值的对象,其本质是一个实现了支持iter()和next()方法的对象,所以,如果想创建一个迭代器,则需要定义一个类,并在该类中实现__iter__和__next__魔法函数

3.1 迭代器创建方法

下面定义一个简单的迭代器,主要是必须实现__iter__和__next__魔法函数,创建时需要注意以下问题

  1. __iter__必须返回一个迭代器
  2. __next__实现数值推演算法
class Myiter:#一般在初始时,传入或者初始化一些实例变量值,便于在__next__中使用def __init__(self):self.a=1#该函数必须返回一个迭代器def __iter__(self):return self#该值返回每次next调用,需要返回的下一个值,其实也就是实现数值推演算法def __next__(self):self.a+=1return self.a#下面实例化一个迭代器
it=Myiter()

3.2 迭代器原理讲解

下面说下,迭代器是如何支持for in 循环体遍历,又是如何在使用next()函数调用时,返回下一个值的

  1. 在使用for in 循环体,比如 for i in it遍历it时,其实调用的是__iter__魔法函数,即for i in it.__iter__()
  2. 在使用next(it)时,其实调用的是__next__魔法函数,即next( it.__next__())
  3. 一般如果定义并实现了__next__,则__iter__直接return self即可,因为此时self就是一个迭代器
  4. 至于如何实现每次运行next返回下一个推导值,是通过实例变量不断记录每次运行推导返回值实现的,下次运行,便可基于上次返回值及推导算法,返回下一个推导值

3.3 内置迭代器

Python的itertools库里面包含了一些生成迭代器的方法,可以生成无限迭代器、有限迭代器以及组合迭代器,具体功能不再展开,先知道,后续有用到了再进行详细了解即可,因为本身用法也比较简单。

3.4 多重迭代器

以上演示的基本都是单重迭代器,即只支持一层for in 循环遍历,因为同一个迭代器只会迭代一次,如果有多层for in 遍历,则只会迭代一层,并且多层遍历其实共用的是同一个迭代器,而内置的str、list等类型,则可以支持多重迭代,如下:

mylist=[1,2]
for i in string:for j in string:print(i,j)
#输出为
1,1
1,2
2,1
2,2

如果按照以上代码,定义自己的迭代器,则因为每次循环,都是循环的同一个迭代器,并不会产生与内置数据类型的效果

class myit:def __init__(self):self.index=0self.data=[1,2]def __iter__(self):return selfdef __next__(self):self.index+=1if self.index>len(self.data):raise StopIterationreturn self.data[self.index-1]
m=myit()
for i in m:for j in m:print(i,j)
#输出
1,2
  1. 以上代码,因为双层for in 循环体,遍历的是同一个m迭代器,所以只会在内层迭代到2之后,便不再迭代
  2. 所以,如果需要支持多重迭代,且不同层的迭代,相互不受影响,需要想办法每个层的迭代都是新的迭代器,我们知道每次for in的时候,均会调用__iter__函数返回一个迭代器,所以需要在该函数内,不再返回自身,而应该返回一个新的迭代器,即创建一个迭代器对象

按照以上思路,将代码改成如下:

class A:def __init__(self):self.index=0self.data=[1,2]def __iter__(self):return selfdef __next__(self):self.index+=1if self.index>len(self.data):raise StopIterationreturn self.data[self.index-1]class B:def __iter__(self):return A()
b=B()for i in b:for j in b:print(i,j)
#输出
1,1
1,2
2,1
2,2
  1. 以上代码,每层for循环,均会调用__iter__函数,返回一个新的迭代器实例对象,这样多重迭代,均有独立的迭代器,就会和内置数据类型的表现基本一致
  2. 当然,以上代码相对比较冗余,其实可以直接在A类中的__iter__函数内,不要返回self,而是创建一个新的实例对象即可
class A:def __init__(self):self.index=0self.data=[1,2]def __iter__(self):return A()def __next__(self):self.index+=1if self.index>len(self.data):raise StopIterationreturn self.data[self.index-1]

四、生成器

生成器本质是一个使用了yield返回值的函数,支持使用next()函数不断返回下一个值,同时支持使用send函数向生成器发送消息

生成的这个特性,为解决 无限个变量和有限内存之间矛盾的问题,提供了解决方案,或者为优化内存使用效率提供了途径

因为比如一个包含1万个变量的列表,和一个包含推导算法的生成器,其内存占用空间,可能前者是后者的几个数量级倍数,比如下面的

a=[i for i in range(10000)] #运行sys.getsizeof(a)后,为87616

a=(i for i in range(10000))#运行sys.getsizeof(a)后,为112,直接减少了8千倍的内存占用空间

4.1 生成器创建方法

4.1.1 使用函数创建

核心点如下:

  1. 函数内部需要实现一个循环体,并实现返回值推导算法,并由yield返回每次推导出来的值
  2. yield关键词,核心作用是
    1. 类似return,将指定值或多个值返回给调用方
    2. 记录此次返回或遍历的位置,返回数值之后,挂起,知道下一次执行next函数,再重新从挂起点接着运行(类似断点的作用)
def generator():a=0b=1while True:c=a+byield ca,b=b,a+b
g=generator()

4.1.2 使用生成器推导式

核心点如下:

  1. 整体规律,类似类表生成推导式
  2. 只是语法,由之前的[],变为()
#使用推导式,对小于10的,乘3,对于大于等于10的,乘5
#此时返回的不再是列表,而是一个生成器
g=(i*3 if i<10 else i*5 for i in range(100)

4.2 yield详解及与return对比

  1. 相同点:

    1. 均在函数体内使用,并且向调用方返回结果
    2. 均可返回一个值或多个值,如果是多个值,则是以元组格式返回
  2. 不同点:
    1. 包含yield的函数,调用时最终返回的是一个生成器,单纯的return函数,调用时返回的是一个值。
    2. return 执行并返回值后,便会直接退出函数体,该函数内存空间即回收释放
    3. yield执行并返回值后,不会退出函数体,而是挂起,待下次next时,再从挂起点恢复运行
    4. yield语句可以接受通过生成器send方法传入的参数并赋值给一个变量,以动态调整生成器的行为表现
    5. yield语句的返回值,可以通过from 关键词指定 返回源
  3. return在生成器中的作用:
    1. 在一个生成器函数中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代

下面通过代码进行演示:

#1、yield和return共存
def gene(maxcount):a,b=0,1count=1while True:if count>maxcount:#直接退出函数体return else:#返回值后,函数在该处挂起,下次再从该处恢复运行yield a+ba,b=b,a+bcount+=1#2、yield接受通过send传入的参数并动态调整生成器行为
#
def gene(maxcount):a,b=0,1count=1while True:if count>maxcount:return else:msg=yield a+bif msg=='stop':returna,b=b,a+bcount+=1
g=gene(10)
next(g)
g.send('msg') #生成器终止,并抛出一个StopIteration异常#3、通过from关键词,接受另外一个生成器,并通过该生成器返回值
#此处只是做展示,大家知道即可,后续如果有类似场景,可以想起来可以这么搞就行
gene1=(i for i in range(10))
def gene2(gene):yield from gene
g=gene2(gene1)

五、基本应用举例

5.1 可控文件读取

使用生成器的挂起并可重新在挂起点运行的特点,我么可以实现按需,每次读取指定大小的文件,避免因为读取文件时,因为一次性读取内容过多,导致内存溢出等问题

def read_file(fpath): BLOCK_SIZE = 1024 with open(fpath, 'rb') as f: while True: block = f.read(BLOCK_SIZE) if block: yield block else: return

5.2 协程

在讲解协程应用之前,先展开讲解下进程、线程、协程概念:

  1. 进程指单独的一个CUP运行程序,可以简单认为一个进程就是一个独立的程序
  2. 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
  3. 协程可以认为是在同一个线程内运行的代码
  4. 进程包含线程,线程包含协程
  5. 进程、线程的切换和调度,一般由操作系统自动完成,具体调度和切换机制较为复杂
  6. 同一线程下,多个协程的切换是由自己编写的代码进行控制,可以实现个性化的调度和切换需求

协程主要有以下特点:

  1. 协程是非抢占式特点:协程也存在着切换,这种切换是由我们用户来控制的。协程主解决的是IO的操作
  2. 协程有极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显
  3. 协程无需关心多线程锁机制,也无需关心数据共享问题:不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多

协程借助生成器实现的基本思路:

  1. 因为生成器通过yield,可以挂起,待下次执行时再次从挂起点恢复运行,满足切换和交替运行的特点
  2. 因为生成器可以通过send函数,动态的干预指定生成器的功能和表现,为实现多个协程之间协作提供了可能

下面代码简单举例用生成器实现协程的机制,后续可以根据自己实际需要,进行具体的实现

#让两个函数交替运行
#核心就是把两个正常的函数使用yield变为生成器函数,然后交替使用其next调用即可
def task1(times):for i in range(times):print('task1 done the :{} time'.format(i+1))yield
def task2(times):for i in range(times):print('task2 done the :{} time'.format(i+1))yield
gene1=task1(5)
gene2=task2(5)
for i in range(100):next(gene1)next(gene2)

5.3 迭代在其他地方的应用表现

其实迭代在Python中应用非常广泛,比如sum、max、min等函数,只要传入一个可迭代的对象,就可以进行工作,这极大的提高了代码的可读性和编程的简洁性。

大家在日常使用Python时,也可以观察或者思考,在需要迭代遍历对象时,是否在使用或者可使用迭代来完成

5.4 常用内置迭代工具

函数 说明 示例
zip(seq1,seq2,seq3,...)

1、将多个序列按位打包成元组,最后返回一个由这些元组组成的序列

2、其返回的结果,本质是一个迭代器,可以尽量减少对内存的占用

seq1=[1,2,3];seq2=[4,5,6];seq3=[7,8,9]

zip(seq1,seq2,seq3)

map(func,seq)

1、对seq序列遍历,并对其每个元素传入func函数

2、其返回的结果,本质是一个迭代器,可以尽量减少对内存的占用

def func(a):

return a+1

seq=[1,2,3]

map(func,seq)

filter(func,seq)

1、对seq序列遍历,并对齐每个元素传入func函数,最后只返回为真的值

2、其返回的结果,本质是一个迭代器,可以尽量减少对内存的占用

def func(a):

return a+1

seq=[1,2,3]

filter(func,seq)

python之迭代器和生成器全解--包含实现原理及应用场景相关推荐

  1. python有关迭代器和生成器的面试题_【面试题 | Python中迭代器和生成器的区别?】- 环球网校...

    [摘要]今天给大家解答一道Python常见的面试题,希望这个面试栏目,给那些准备面试的同学,提供一点点帮助!小编会从最基础的面试题开始,每天一题.如果参考答案不够好,或者有错误的话,麻烦大家可以在留言 ...

  2. python中的format什么意思中文-Python中format()格式输出全解

    格式化输出:format() format():把传统的%替换为{}来实现格式化输出 1.使用位置参数:就是在字符串中把需要输出的变量值用{}来代替,然后用format()来修改使之成为想要的字符串, ...

  3. Python的迭代器和生成器

    Python的迭代器和生成器 一.迭代器Iterators 迭代器仅是一容器对象,它实现了迭代器协议.它有两个基本方法: 1)next方法 返回容器的下一个元素 2)__iter__方法 返回迭代器自 ...

  4. python数据分析——pyecharts折线图全解

    折线图是排列在工作表的列或行中的数据可以绘制到折线图中.折线图可以显示随时间(根据常用比例设置)而变化的连续数据,因此非常适用于显示在相等时间间隔下数据的趋势. 下面我给大家介绍一下如何用pyecha ...

  5. python全栈之巅_Python 迭代器、生成器详解 - Python全栈之巅

    迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退. 使用迭代器的优点 对于 ...

  6. python生成器单线程_【Python】迭代器、生成器、yield单线程异步并发实现详解

    转自http://blog.itpub.net/29018063/viewspace-2079767 大家在学习python开发时可能经常对迭代器.生成器.yield关键字用法有所疑惑,在这篇文章将从 ...

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

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

  8. Python之迭代器和生成器(Day17)

    一.可迭代对象(iterable) 刚才说过,很多容器都是可迭代对象,此外还有更多的对象同样也是可迭代对象,比如处于打开状态的files,sockets等等.但凡是可以返回一个迭代器的对象都可称之为可 ...

  9. python之迭代器,生成器

    一,迭代器 1.1什么是可迭代对象? 字符串.列表.元组.字典.集合都可以被for循环,说明他们都是可迭代的. 我们怎么来证明这一点呢? from collections import Iterabl ...

  10. python后台架构Django开发全解

    全栈工程师开发手册 (作者:栾鹏) python教程全解 我的使用环境win8+python3.6+pycharm+django2.0 博主使用的是anaconda佩戴的python3.6,所以pyt ...

最新文章

  1. Lync Server 2010迁移至Lync Server 2013部署系列 Part2:部署后端主服务器
  2. iOS开发拓展篇—CoreLocation简单介绍
  3. USCAO Job Processing 4.2(贪心,不知道叫啥方法)
  4. oracle中字典指的是什么,ORACLE数据库中什么是数据字典及作用
  5. abap 向上取整CEIL和向下取整FLOOR
  6. TP5的目录常量和路径
  7. Android 动画(一)---布局动画
  8. [转]面向对象的六大原则
  9. Android研发中对String的思考(源码分析)
  10. windows里面的批处理命令不停地处理同一条命令
  11. 软件工程导论 00章数据流图与数据字典
  12. struts-step
  13. 华为机试HJ100:等差数列
  14. 马云入选全球“十大思想者”,成唯一获选的中国企业家
  15. pytorch tensor_Pytorch之Tensor操作
  16. javascript自动填写表单小技巧
  17. jsp分页功能的位置有可能会影响到翻页时的查询条件
  18. vue中使用图片裁切器
  19. SEO-老域名的选择
  20. informix和mysql对接_优化Informix数据库访问

热门文章

  1. 反垃圾邮件智能网关之梭子鱼
  2. 称重仪表显示ol怎么解决_称重仪表显示Erd和数字是怎么回事?
  3. Web开发框架——Zheng
  4. 【Unity3D开发小游戏】《青蛙过河》Unity开发教程
  5. MeeGo系统Atom处理器 神秘设备现身俄罗斯
  6. 餐巾计划问题【网络流24题】
  7. 解决checkbox复选框未选中时不传值的问题 / 判读复选框是否选中
  8. 二次开发Spark实现JDBC读取远程租户集群Hive数据并落地到本集群Hive的Hive2Hive数据集成【Java】
  9. 形容java工作者的句子_形容工作态度的句子
  10. unity Reflection Probe