8、迭代器与生成器
1、什么是迭代
使用for循环遍历取值的过程叫做迭代
- 迭代列表
list1 = [11, 22, 33, 44]
for i in list1:
print(i)
# 11
# 22
# 33
# 44
- 迭代元组
tuple1 = (11, 22, 33, 44)
for i in tuple1:
print(i)
# 11
# 22
# 33
# 44
- 迭代字符串
str1 = "abcde"
for i in str1:
print(i)
# a
# b
# c
# d
# e
- 迭代字典
dic1 = {'name': 'zht', 'age': 25, 'school': 'zhbit'}
- -默认迭代的是 键
for i in dic1:
print(i)
# name
# age
# school
-
- 迭代字典的值
for i in dic1.values():
print(i)
# zht
# 25
# zhbit
-
- 迭代字典的键和值
for i1, i2 in dic1.items():
print(i1, i2)
# name zht
# age 25
# school zhbit
- 迭代集合
s = {'apple', 'banana', 'pear', 'orange'}
for i in s:
print(i)
# apple
# pear
# orange
# banana
2、可迭代对象(Iterable
)
可以使用for循环遍历取值的对象(迭代是过程,可迭代对象是对象),就是可迭代对象。如上面的例子,列表、元组、字典、集合、字符串都是可迭代对象
可以使用 isinstance()
函数判断该对象是否是可迭代对象
In: from collections import Iterable # 引入模块
In: isinstance([1,2,3,4],Iterable) # 判断列表是否是可迭代对象
Out: True
In: isinstance((1,2,3,4),Iterable) # 判断元组是否是可迭代对象
Out: True
In: isinstance("abcdef",Iterable) # 判断字符串是否是可迭代对象
Out: True
In: isinstance({'name':'zht','age':25,'school':'zhbit'},Iterable) # 判断字典是否是可迭代对象
Out: True
In: isinstance(100,Iterable) # 判断数字(Numbers)是否是可迭代对象
Out: False
3、推导式
list(range(1, 5)) # 注意range(1, 5)是左闭右开,1 2 3 4
# [1, 2, 3, 4]
[x*x for x in range(1, 5)]
# [1, 4, 9, 16]
[x for x in range(1, 10) if x % 2 == 0]
# [2, 4, 6, 8]
# 过滤掉长度小于等于3的字符串列表,并将剩下的转换成大写字母
names = ['Bob', 'Tom', 'alice', 'Jerry', 'Wendy', 'Smith']
[name.upper() for name in names if len(name) > 3]
# ['ALICE', 'JERRY', 'WENDY', 'SMITH']
# 求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表
[(x, y) for x in range(6) if x % 2 == 0 for y in range(6) if y % 2 != 0]
# [(0, 1), (0, 3), (0, 5), (2, 1), (2, 3), (2, 5), (4, 1), (4, 3), (4, 5)]
4、生成器
通过列表生成式,我们可以直接创建一个列表。但是假如我们有如下推导式,创建100万个元素的列表。这个list就占用很大内存。如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
[x*x for x in range(1, 1000000)]
所以,如果列表元素可以按照某种算法推算出来(推导式或函数),那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。
在Python中,这种一边循环一边计算的机制,称为生成器(
generator
)
4.1、创建生成器
- 方法1:即把列表生成式的
[ ]
改成( )
——生成器表达式
推导式:
l=[x for x in range(1, 10) if x % 2 == 0]
l
# [2, 4, 6, 8]
print(type(l))
# <class 'list'>
# 通过for循环打印每个元素
for i in l:
print(i)
# 2
# 4
# 6
# 8
生成器:把列表推导式的[ ]
改成( )
g=(x for x in range(1, 10) if x % 2 == 0)
g # 如下结果,generator object,即生成器对象
# <generator object <genexpr> at 0x000001E0798AB040>
print(type(g)) # 打印g的类型
# <class 'generator'>
生成器保存的是算法,可以通过next()
函数获得生成器的下一个元素的值。每次调用next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
next(g)
# 2
next(g)
# 4
next(g)
# 6
next(g)
# 8
next(g)
# StopIteration:
我们创建了一个生成器后,基本上永远不会用next()
调取,而是通过for循环来迭代它,并且不需要关心StopIteration
的错误。既然生成器可以被for循环迭代,那么生成器也是可迭代对象。
g1 = (x for x in range(1, 10) if x % 2 == 0) # 注意range(1, 10)是左闭右开,1 2 3 4 5 6 7 8 9
for i in g1:
print(i)
# 2
# 4
# 6
# 8
- 方法2:函数加
yield()
关键字——生成器函数
比如我们有一个f1(max)
函数,输入最大值,然后从0开始打印至最大值。如下:
def f1(max):
i=0
while i <=max:
print(i)
i+=1
return None
f1(10)
# 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 10
那如何将f1(max)
函数修改成生成器呢?只需要把 print(i)
改为 yield(i)
就可以了
- 如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个生成器。
- 区分普通函数和生成器函数,普通函数调用直接返回结果,生成器函数的“调用”实际返回一个生成器对象。
def f1(max):
i=0
while i <=max:
yield(i) # yield关键字
i+=1
return None
f1(10)
# <generator object f1 at 0x000001E078DE24A0>
for i in f1(10): # 通过for循环调出
print(i)
# 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 10
我们在定义一个与f1
一样的生成器f2
,并用next
函数调用,如下:
def f2(max):
i=0
while i <=max:
yield(i)
i+=1
return None
g = f2(10)
next(g)
# 0
next(g)
# 1
next(g)
# 2
next(g)
# 3
4.2、生成器的执行流程
- 函数是顺序执行,遇到
return
语句或者最后一行函数语句就返回。 - 变成生成器的函数,每次调用
next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
In: def gen():
print("步骤1")
yield(1)
print("步骤2")
yield(3)
print("步骤3")
yield(5)
g=gen()
print(type(g))
Out: <class 'generator'>
In: next(g)
Out: 步骤1
1
In: next(g)
Out: 步骤2
3
In: next(g)
Out: 步骤3
5
In: next(g)
Out: ---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-24-e734f8aca5ac> in <module>
----> 1 next(g)
StopIteration:
5、迭代器
- 我们现在已经知道,
List
、Tuple
、Dict
、Set
、Str
等数据类型、以及生成器都可以用for进行迭代,故他们都是可迭代对象。 - 生成器不但可以被for进行迭代,还可以被
next()
函数进行调用,直到抛出StopIteration
异常 - 可以被
next()
函数调用并不断返回下一个值的对象称为迭代器 - 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束,迭代器只能往前不会后退
- 迭代器有两个基本的方法:
iter()
和next()
为什么
List
、Tuple
、Dict
、Set
、Str
等数据类型是可迭代对象,但不是迭代器呢?
- 这是因为迭代器对象是一个数据流,里面只保存算法
- 可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过
next()
函数实现按需计算下一个数据,所以迭代器的计算是惰性的,只有在需要返回下一个数据时它才会计算- 迭代器的甚至可以表示一个无限大的数据流,例如全体自然数。而使用
list
等数据类型是永远不可能存储全体自然数的
- 可以利用
iter()
函数将List
、Tuple
等数据类型转化为迭代器
In: # 字符串创建迭代器
str1="abcdefgh"
iter1=iter(str1)
print(type(iter1))
Out: <class 'str_iterator'>
In: # 列表创建迭代器
list1=[1,2,3,4,5]
iter2=iter(list1)
print(type(iter2))
Out: <class 'list_iterator'>
In: # 元组创建迭代器
tup1=(1,2,3,4,5)
iter3=iter(tup1)
print(type(iter3))
Out: <class 'tuple_iterator'>
6、迭代器和生成器的区别
共同点
- 生成器与迭代器,都可以被
next()
调取,故生成器也是一种迭代器。
不同点
- 语法上:
生成器是通过函数的形式中调用yield
或()
的形式创建的(生成器函数与生成器表达式);而迭代器可以通过iter()
内置函数创建。 - 用法上:
生成器在调用next()
函数或for循环中,所有过程被执行,且返回值;迭代器在调用next()
函数或for循环中,所有值被返回,没有其他过程或动作。