Python 手记(一):生成器与迭代器
一、列表生成式
在引入生成器和迭代器之前,首先还得提起列表生成式。List Comprehensions,是Python内置的非常简洁强大的用作创建list列表的生成式。
假设:list1=[1,2,3,4,5,6,7,8,9,10],要求将列表内每个元素的值乘以2,怎么做呢?
Way1:新建一个list
1 | >>> |
way2:原list修改值
1 | >>> |
有没有更简洁的办法呢? of course,List Comprehensions!
1 | >>> |
还可以加上条件判断,例如取出list1中能被2整除的元素,乘以21
2
3
4
5
6>>> list1=[1,2,3,4,5,6,7,8,9,10]
>>>
>>> list2=[i*2 for i in list1 if i % 2 ==0]
>>>
>>> print(list2)
[4, 8, 12, 16, 20]
双重循环加条件判断:
1 | >>> a=[1,2,3] |
不仅是list,for循环甚至可以用多个变量来迭代dict的key和value:
1 | # _*_conding:utf-8 _*_ |
那么同样,可以用生成式来将dict生成list
1 | # _*_conding:utf-8 _*_ > |
二、生成器Generator
上方描述了列表生成式是一次性将所有元素都装入生成的list中,读取list时也是一次性全部读取,list是python最常用的数据类型之一,当数据规模非常大时(百万千万级别),如果继续将如此大规模的数据一次性读取进内存中再进行下一步操作,会带来很长的等待时间和资源消耗,而通常我们所要读取的是列表中的某一部分元素而不是全部,由此引入了generator的概念。
Generator的工作方式是:惰性运算,只保存算法不保存值,边循环边计算,仅当需要进入下一次循环时,才计算出list中的下一个元素,而不必创建完整的list.
Generator在python里的创建方式中最简单的方法为:将生成式的[]修改为()即可生成一个generator,它的调用方法为内置的next()方法(或.next),每next()一次则运算一次,且顺序执行指针方向不可逆,list遍历结束后,会报错StopIteration:
1 | >>> list1=[1,2,3,4,5,6,7,8,9,10] |
通常调用生成器,也不会用next()方法一次次地来调用,而是使用for循环,因为生成器也是可迭代对象:1
2
3
4
5
6
7
8
9
10
11
12>>> list1=[1,2,3,4,5,6,7,8,9,10]
>>> a=(i*2 for i in list1 if i % 2 ==0)
>>> a
<generator object <genexpr> at 0x0000000000D380F8>
>>> for i in a : print(i)
...
4
8
12
16
20
>>>
当generator的使用环境比较复杂,无法通过修改for循环的生成式来创建生成器满足需求时,可以引入函数。
创建一个普通的计算二进制计算循环函数:
1 | >>> def binary(maxnum): |
不难发现,这个函数计算的结果是有规则性的,可以根据前一个结果推算出后一个结果,那么这个函数距离generator已经非常近了,仅需一个yield即可。将print(n)改成yield(n)即可实现将函数变为生成器:
1 | >>> def binary(maxnum): |
以上可以看出,函数变成了可以使用for循环的可迭代对象,但是执行结果没有return返回值,为什么呢?因为加入了yield关键字,把function变为了generator。
个人理解,yield在生成器中的起到断点的作用,每次调用next()时,遇到yield语句就中断,下一次再调用next()时再从上次中断的yield处开始下一轮循环。(类似游戏中的存档~)
再来看一个yield最简单的用法:
1 | > |
可以看出,每执行一次next()函数调用,遇到yield后,函数的执行便卡住返回,下一次调用时,再从上一次yield处开始。直至生成器内的元素遍历完成后第四次调用next()返回StopIteration的报错。
再对比一下上方的的for循环的函数式生成器,可以发现,使用for循环时,生成式元素遍历完毕后,不会报错,而使用next()函数调用时,元素遍历完毕后再次next()会返回报错StopIteration,这即是生成式的一种返回值,类似函数里的return code。可以根据返回值来定义一些后续的操作,再次拿上方的二进制计算生成器来举例:
1 | def binary(maxnum): |
解析:try + except 配合使用,将接收到StopIteration作为执行完成的信号,singal.value的值为函数生成式return的值。
三、迭代器
在python中,可以被for循环遍历的对象,被称为可迭代对象,Iterable。其中包括常见的数据类型,包括list、tuple、dict、set、str等,另一种则是generator和带yield的generator function。
可以使用isinstance()判断一个对象是否是Iterable对象。
1 | > |
但是,Iterable对象并不代表着它就是迭代器Iterator。
只有能调用next()函数不断返回下一个值的对象才能被称为迭代器,因此,生成器绝对是迭代器,但迭代器不一定是生成器,这个概念要理清。可以使用isinstance()判断一个对象是否是Iterator。
1 | from collections import Iterator //从collection库导入Iterator模块 |
为什么list、tuple、dict、str都是Iterable可迭代对象,却不是Iterator迭代器呢?因为在python中,关于Iterator对象的定义是:可以被next()函数调用并不断返回下一个数据,直到没有数据时StopIteration的一个类似有序序列的数据流,无法提前得知这个数据流的长度,只能不断通过next()函数去惰性计算下一个元素,而不是一次性全部计算出。
有没有办法实现将list、tuple、dict、str转换成成Iterator呢?
1 | >>> from collections import Iterator |
使用Iter()函数,可以将Iterable对象转换成Iterator。
附记:
python中的for循环本质上就是通过不断调用next()函数实现的,例如,以下for循环和下方调用迭代器方式的循环,两者本质上是完全一致的:
1 | list=[1,2,3] |
1 | from collections import Iterator |
Over,过几天再总结归纳一下python装饰器~