所谓的 迭代器 就是用来迭代取值的工具
而迭代是重复反馈过程的活动,其目的通常是为了逼近所需的目标或结果,每一次对过程的重复称为一次迭代,而每一次迭代得到的结果会作为下一次迭代的初始值,单纯的重复并不是迭代。
while True:
inp_uname = input('请输入一个数:').strip()
print(inp_uname)
# 结果
请输入一个数:1
1
请输入一个数:2
2
请输入一个数:3
3
请输入一个数:4
4
下述while循环才是一个迭代过程,不仅满足重复,而且以每次重新赋值后的index值作为下一次循环中新的索引进行取值,反复迭代,最终可以取尽列表中的值
goods = ['iPhone Pro', 'MacBook Pro', 'iPad Pro', 'Watch']
index = 0
while index < len(goods):
print(goods[index])
index += 1
# 结果
iPhone Pro
MacBook Pro
iPad Pro
Watch
通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串、列表、元组。对于没有索引的字典、集合等非序列类型,必须找到一种不依赖索引来进行取值的方式,这就用到了迭代器。
要想了解迭代器是什么?必须先搞清楚一个很重要的概念:可迭代对象(iterable)。从语法形式上讲,内置的有__iter__方法的对象都是可迭代对象,比如说:字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。
print('hello world'.__iter__())
print(['123', '456'].__iter__())
print(('123', '456').__iter__())
print({'name': 'lmy', 'age': 22}.__iter__())
print({'lmy', 'lyw', 'lyx'}.__iter__())
with open('a.txt', 'r') as f:
print(f.__iter__())
# 结果
<str_iterator object at 0x0000020A7CF00B50>
<list_iterator object at 0x0000020A7CF00B50>
<tuple_iterator object at 0x0000020A7CF00B50>
<dict_keyiterator object at 0x0000020A7CEEA390>
<set_iterator object at 0x0000020A7CEDFA00>
<_io.TextIOWrapper name='a.txt' mode='r' encoding='cp936'>
调用*obj.iter()*方法返回的结果就是一个迭代器对象(iterator)。
迭代器对象是内置有 iter() 和 next() 方法的对象,打开的文件本身就是一个迭代器对象,执行迭代器对象*.iter()*方法得到的仍然是迭代器本身,而执行迭代器的 .next() 方法就会计算出迭代器中的下一个值。
迭代器是Python提供的一种统一的、不依赖于索引的迭代取值的方式,只要存在多个值,无论序列类型还是非序列类型都可以按照迭代器的方式取值。
s = {1, 2, 3} # 可迭代对象s
i = iter(s) # 本质就是在调用s.__iter__(),返回s的迭代器对象i
print(next(i)) # 本质就是在调用i.__next__()
print(next(i))
print(next(i))
print(next(i))
# 结果
1
2
3
Traceback (most recent call last):
File "C:\Users\xiaowu\Desktop\pythnon-blog\python\python.py", line 6, in <module>
print(next(i))
StopIteration
为什么已经升级成为迭代器对象以后,却还是拥有*iter()方法,这就让我们很是疑惑了,我们知道一个可迭代对象拥有iter()方法的原因是为了将可迭代对象转换成为迭代器对象,但是已经是迭代器对象了为什么还有iter()*方法呢?
原因:迭代器对象的*iter()方法是为了符合for循环的原理而存在的,因为for循环在工作的时候每次都会先调用可迭代对象的iter()方法,所以迭代器对象也拥有iter()*方法。
有了迭代器之后,我们便可以不依赖索引迭代取值了,使用while循环的实现方式如下:
goods = ['iPhone Pro', 'MacBook Pro', 'iPad Pro', 'Watch']
goods_iter = goods.__iter__() # 拿到迭代器对象
while True:
try:
print(goods_iter.__next__()) # 调用迭代器对象的__next__()方法
except StopIteration: # 捕捉异常终止循环
break
# 结果
iPhone Pro
MacBook Pro
iPad Pro
Watch
for循环又称之为迭代循环,in的后面可以跟随任意可迭代对象,上述while循环可以简写为
goods = ['iPhone Pro', 'MacBook Pro', 'iPad Pro', 'Watch']
for item in goods:
print(item)
# 结果
iPhone Pro
MacBook Pro
iPad Pro
Watch
for循环在工作的时候,首先会调用可迭代对象goods内置的*iter()方法拿到一个迭代器对象,然后再调用该迭代器对象的next()*方法将取到的值赋值给item,执行循环体完成一次循环,周而复始,直到捕捉StopIteration异常,迭代结束
基于索引的迭代取值,所有迭代的状态都保存在了索引中,而基于迭代器实现迭代的方式不再需要索引,所有迭代的状态就保存在迭代器中,然而这种处理方式优点与缺点并存
1.为序列和非序列类型提供了一种统一的迭代取值的方式
2.惰性计算:迭代器对象表示的是一个数据流,可以只在需要的时候才调用*next()*来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,比如说:列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。
1.除非取尽,否则无法获取迭代器的长度
2.只能取下一个值,不能回到开始,更像是一次性的,迭代器产生后的唯一目标就是重复执行 next() 方法直到值取尽,否则就会停留在某个位置,等待下一次调用 next() 。
3.如果想要再次迭代,只能重新调用可迭代对象的 iter() 方法来创建一个新的迭代器对象。
4.如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环取到值。
goods = ['iPhone Pro', 'MacBook Pro', 'iPad Pro', 'Watch']
goods_iter = goods.__iter__()
while True:
try:
print(goods_iter.__next__())
except StopIteration:
break
for item in goods_iter:
print(item)
1.如果你知道你可以循环遍历某个对象,这是一个可迭代对象。
2.如果你知道你正在循环遍历的对象是在循环的时候计算出来,那么这是一个惰性可迭代对象(lazy iterable)。
3.如果你知道你可以传递一些东西给next函数,它就是一个迭代器(这是最常见的惰性可迭代对象)。
4.如果你可以循环多次而不用「耗尽」它,它不是一个迭代器。
5.如果你不能将某些东西传递给next函数,那么它不是一个迭代器。
6.Python3 的 range 对象不是迭代器。 如果你正在指导别人关于 range 对象的知识,请不要使用「迭代器」一词,这会让人十分困惑,并可能导致他人开始滥用「迭代器」这个词
如果函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值也就是生成器对象
def my_range(start, stop, step=1):
print(f'start...')
while start < stop:
yield start
start += step
print('end...')
g = my_range(0, 6)
print(g)
# 结果
<generator object my_range at 0x00000233FCAF9A10>
生成器内置有*iter和next*方法,所以生成器本身就是一个迭代器
def my_range(start, stop, step=1):
print(f'start...')
while start < stop:
yield start
start += step
print('end...')
g = my_range(0, 6)
print(g.__iter__)
print(g.__next__)
# 结果
<method-wrapper '__iter__' of generator object at 0x000002F591AF9A10>
<method-wrapper '__next__' of generator object at 0x000002F591AF9A10>
因而我们可以用*生成器.next()*触发生成器所对应函数的执行
def my_range(start, stop, step=1):
print(f'start...')
while start < stop:
yield start
start += step
print('end...')
g = my_range(0, 5)
print(g.__next__()) # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数
print(g.__next__()) # 再次调用,函数从上次暂停的位置继续执行,直到遇到新的yield
print(g.__next__())
print(g.__next__()) # 周而复始
print(g.__next__())
print(g.__next__()) # 触发函数执行没有遇到yield则没有值返回,即取值完毕抛出异常结束迭代
# 结果
start...
0
1
2
3
4
end...
Traceback (most recent call last):
File "C:\Users\xiaowu\Desktop\pythnon-blog\python\python.py", line 14, in <module>
print(g.__next__()) # 触发函数执行没有遇到yield则没有值返回,即取值完毕抛出异常结束迭代
StopIteration
既然生成器属于迭代器,那么必然可以使用for循环来进行迭代,如下所示:
def my_range(start, stop, step=1):
print(f'start...')
while start < stop:
yield start
start += step
print('end...')
for i in my_range(0, 5):
print(i)
# 结果
start...
0
1
2
3
4
end...
有了yield关键字,我们就有了一种自定义迭代器的实现方式,yield可以用于返回值,但不同于return,函数一旦遇到了return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值。
在函数内可以采用表达式形式的yield,此话怎么讲?如下代码所示:
def eater():
print(f'准备要吃啦...')
while True:
food = yield
print(f'得到一个食物:{food},开始吃掉它...')
# 可以拿到函数的生成器对象持续为函数体send值,如下所示:
g = eater() # 得到生成器对象
g.__next__() # 需要事先"初始化"一次,让函数挂起在food = yield,等待调用g.send()方法为其传值
g.send('水果玉米')
g.send('奥尔良烤鸡')
# 结果
准备要吃啦...
得到一个食物:水果玉米,开始吃掉它...
得到一个食物:奥尔良烤鸡,开始吃掉它...
针对表达式形式的yield,生成器对象必须事先被初始化一次,让函数挂起在food=yield的位置,等待调用*g.send()*方法为函数体传值,g.send(None)等同于g.next()
我们可以编写装饰器来完成为所有表达式形式yield对应生成器的初始化操作,如下所示:
def init(func):
def wrapper(*args, **kwargs):
g = func(*args, **kwargs)
g.__next__()
return g
return wrapper
@init
def eater():
print(f'准备要吃啦...')
while True:
food = yield
print(f'得到一个食物:{food},开始吃掉它...')
generator = eater()
generator.send('水果玉米')
generator.send('烧鸡')
# 结果
准备要吃啦...
得到一个食物:水果玉米,开始吃掉它...
得到一个食物:烧鸡,开始吃掉它...
def eater():
print(f'准备要吃啦...')
food_list = []
while True:
food = yield food_list
print(f'得到一个食物:{food},开始吃掉它...')
food_list.append(food)
print(food_list)
generator = eater()
generator.__next__()
generator.send('水果玉米')
generator.send('烧鸡')
# 结果
准备要吃啦...
得到一个食物:水果玉米,开始吃掉它...
['水果玉米']
得到一个食物:烧鸡,开始吃掉它...
['水果玉米', '烧鸡']
三元表达式是Python为我们提供的一种简化代码的解决方案,语法如下:
res = 条件成立的时候返回的值 if 条件 else 条件不成立的时候返回的值
不使用三元表达式
def lmy_max(x, y):
if x > y:
return x
else:
return y
res = lmy_max(100, 300)
print(res)
# 结果
300
使用三元表达式
x = 1
y = 2
res = x if x > y else y # 三元表达式
[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN
]
类似于
#类似于
res=[]
for item1 in iterable1:
if condition1:
for item2 in iterable2:
if condition2
...
for itemN in iterableN:
if conditionN:
res.append(expression)
没使用列表生成式之前
egg_lis = []
for i in range(10):
egg_lis.append(f'鸡蛋{i}')
print(egg_lis)
# 结果
['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
使用列表生成式之后
print([f'鸡蛋{i}' for i in range(10)])
# 结果
['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
创建一个生成器对象,一种是调用带yield关键字的函数,另一种就是生成器表达式,与列表生成式的语法格式相同,只需要将**[]换成()**,也就是:
(expression for item in iterable if condition)
对比列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象
print([i ** 2 for i in range(10)])
print((i ** 2 for i in range(10)))
# 结果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x7fdc79341c80>
对比列表生成式,生成器表达式的优点自然是节省了内存(一次只产生一个值在内存中)
gen = (i ** 2 for i in range(3))
print(gen.__next__())
print(gen.__next__())
print(gen.__next__())
print(gen.__next__()) # 抛出异常StopIteration
如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成
with open('udata.json', 'rb') as f:
nums = (line.__len__() for line in f)
total_size = sum(nums) # 依次执行nums.__next__(),然后累加到一起得到结果
print(total_size)
print([i for i in range(10) if i > 2])
[3, 4, 5, 6, 7, 8, 9]
{k:v for i in range()}
print({s for s in range(5)})
{0, 1, 2, 3, 4}