Python基础:08.迭代器与生成器
一、迭代
通过for循环来遍历list、tuple或char,称为迭代
1 |
|
1
2
1
2
p
y
dict默认是以key进行迭代。如果要迭代value,可以用for value in dict.values()
。同时迭代key和value,可以用for k, v in d.items()
1 |
|
1
2
3
python
java
c++
1 python
2 java
3 c++
二、可迭代对象(Iterable)
能用for循环进行迭代的对象就是可迭代对象,判断一个对象是可迭代对象的方法:通过collections.abc
模块的Iterable
类型判断
可迭代的对象:str,list,tuple,dict,set,文件对象
1 |
|
True
False
如果要给list加下标,可以用Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身
1 |
|
0 A
1 B
2 C
三、列表生成式
学完了可迭代对象,就可以学习一个很强大的工具,列表生成式,他是Python内置的非常简单却强大的可以用来创建list的生成式,即在可迭代对象中将满足条件的元素生成为一个列表,条件可以省略
其结构为:[x表达式 for x in 可迭代对象 if 判断语句]
,它与下面的for循环是等价的
1 |
|
使用:
1 |
|
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
1. 表达式用法
列表生成式中的表达式有多种用法
(1)可以调用方法
比如知道可迭代对象L中只有字符串的话,就可以使用字符串的方法
1 |
|
[‘hello’, ‘world’, ‘apple’]
1 |
|
[‘hello’, ‘world’, ‘apple’]
(2)使用三元表达式
x表达式可以使用三元表达式进一步确定x的值,首先回顾下python里的三元表达式
1 |
|
务必注意:因为涉及到if-else,要小心 else 语句放在 for 语句前后位置的区别,for语句前面必须是一个表达式,它必须根据元素计算出一个结果,可迭代对象后面才是一个筛选条件。因此需要将完整的三元表达式放在for的前面,for后面可以有if,但不能有else,列表生成式的语法不支持for后面有else
正确示范:
1 |
|
[0, 2, 0, 4, 0, 6, 0, 8, 0, 10]
1 |
|
[2, 4, 6, 8, 10]
错误示范:
1 |
|
SyntaxError: invalid syntax
1 |
|
SyntaxError: invalid syntax
2. 可迭代对象用法
列表生成式的for循环可以使用两个甚至多个变量
1 |
|
[‘x=A’, ‘y=B’, ‘z=C’]
3. 嵌套列表生成式
当在表达式中出现两个对象,或者是嵌套的列表等情况就可以使用两层循环,嵌套列表生成式执行的顺序一定是和写成for循环的顺序相同,一般也就是先执行第一个for循环。三层及以上的用的不多,可读性较差
1 |
|
[2, 3, 4, 6, 6, 9, 8, 12]
1 |
|
[‘AX’, ‘AY’, ‘BX’, ‘BY’, ‘CX’, ‘CY’]
1 |
|
[1, 2, 3, 4, 5, 6, 7, 8, 9]
1 |
|
[1, 2, 3, 4, 5, 6, 7, 8, 9]
还有一个情况是列表生成式中的列表生成式,他与嵌套列表生成式是不同的,它是先执行后面的for语句,即把后面的for循环放在最外层循环
他是在列表中生成列表,注意区分
1 |
|
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
1 |
|
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
1 |
|
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
1 |
|
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
4. 集合生成式
把列表生成式的[]
换为{}就成为集合生成式
1 |
|
{1, 2, 3, 4, 6}
1 |
|
{1, 2, 3, 4, 6}
5. 字典生成式
除了把列表生成式的[]
换为{},在最开始的表达式中需要写成键值对的形式,这样就成为了字典生成式
1 |
|
{0: ‘a’, 1: ‘as’, 2: ‘bat’, 3: ‘car’, 4: ‘dove’, 5: ‘python’}
使用小括号包裹生成式会变成生成器对象,而不是元组生成式
四、迭代器(Iterator)
迭代器指的是迭代取值的工具,每次调用迭代器会返回自身的下一个元素,它表示的是一个数据流,可以看做是一个有序序列,但不能提前知道序列长度,只有在需要返回下一个数据时它才会计算。所以Iterator的计算是惰性的,和列表等集合数据类型不同
迭代器必须有两个基本的方法:
iter()
,这是可迭代对象的内置函数,将可迭代对象转变为迭代器next()
可以输出迭代器的下一个元素
从类角度讲,任何实现了__iter__()
和__next__()
方法的对象都是迭代器
为了防止出现无限循环的情况,StopIteration
异常用于标识迭代的完成,没有数据时抛出StopIteration异常,这并不是错误,只是提醒迭代完成
1 |
|
1
2
3StopIteration Traceback (most recent call last)
in ()
4 print(next(iter1))
5 print(next(iter1))
—-> 6 print(next(iter1))
迭代器一定是可迭代对象,但可迭代对象不一定是迭代器,如list、dict、str等是Iterable但不是Iterator,需要经过iter()函数转换 ,常用的列表生成式就是迭代器
可以用collections.abc
模块的Iterator
类型判断一个对象是否是Iterator对象
1 |
|
True
False
True
True
True
一般更常用的是用for循环遍历迭代器内容,这也是for循环的底层原理。for循环本质就是基于迭代器:for 循环在处理这些数据前,会调用 iter() 方法,将这些数据转化为一个迭代器,然后调用迭代器的 next() 方法,并捕获StopIteration异常,也就实现了遍历完所有数据就会结束,并不会抛出这个异常 。所以从for循环的角度,但凡可以被for循环取值的对象就是可迭代对象
1 |
|
1
2
3
迭代器的优缺点:
- 优点:
- 提供了一种通用不依赖索引的迭代取值方式
- 同一时刻在内存中只存在一个值,更节省内存
- 缺点:
- 取值不如按照索引的方式灵活,不能取指定的某一个值,只能往后取,不能往前取
- 无法预测迭代器的长度
五、生成器(Generator)
迭代器解决了特大序列占内存的问题,但当生成序列的算法较为复杂时,需要一个能更简洁代码的迭代器,而不是一直定义类,这就有了生成器(个人理解)
在 Python 中,将使用了yield
语句的函数赋给一个对象,这个对象被称为生成器
生成器和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成生成器的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行,所以需要给生成器设置一个条件来退出循环,不然就会无限产生内容出来。生成器本质上也是迭代器
所以,生成器仅仅保存了一套生成数值的算法,并且没有让这个算法现在就开始执行,而是什么时候调它,它什么时候开始计算一个新的值并返回,是一种懒汉式的思想。
同时也要注意,使用了yield
语句的函数并不是生成器,它只是函数,必须要将它赋给一个对象,这个对象才是生成器
1 |
|
<class ‘function’>
<class ‘generator’>
1
1
2
3
5
8
值得注意的是,上面的代码中要想得到函数的return返回值,是做不到的,因为yield就返回了,所以如果想要得到返回值,必须捕获StopIteration
错误,函数的返回值就包含在StopIteration的value中。而若只使用for循环或者list()函数,python并不会抛出这个异常,所以需要使用while循环加上next()函数。因此,编写和应用生成器时,看是否需要返回值,来选择用for/list()还是next()
1 |
|
1
1
2
3
5
8
Generator return value: done
另外,只要把一个列表生成式的[]
改成()
,也能创建一个generator
1 |
|
generator
迭代器和生成器都常被用于使用list()函数来生成列表
在写接受任意序列类型(列表、元组、N维数组),甚至是一个迭代器的函数时,可以先检查这个对象是否是列表,如果不是就将其转为列表
1 |
|
[1, 2, 3]