title: 迭代器与生成器 #标题tags: python #标签
date: 2021-08-29
categories: python # 分类
每一个可迭代对象中,必定包含一个__iter__
方法,比如字符串、字典、元组、列表。
生成器与迭代器区别不大,生成器与迭代器可以看成是一种,生成器的本质就是迭代器,唯一的区别就是生成器是我们自己用python代码构建的数据结构,迭代器都是python提供的,或者我们自己用iter函数将数据转换成迭代器的。
参考:
可迭代对象优缺点
优点:
- 存储的数据直接能显示,比较直观;
- 拥有的方法比较多,操作方便;
缺点:
- 占用内存比较多
- 不建议直接通过for循环取值,效率过低,可以通过索引、key进行取值。之所以可以通过for循环进行取值,是因为python内部给我们做了进一步处理。
判断一个数据是否为可迭代对象
a = 'abcdefg'
# 判断字符串的所有方法中是否包含__iter__,包含则为一个可迭代对象
print('__iter__' in dir(a))
# dir方法用于获取一个函数的所有方法,如下:
a = 'abcdefg'
print(dir(a))
# 打印如下:
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
迭代器的定义
从字面意思来讲,迭代器就是一个可更新迭代的工具,从专业角度来讲,内部同时含有__iter__
和__next__
方法的对象就是迭代器。可以通过如下代码来判断是不是一个迭代器:
with open('周四练习', encoding='utf-8', mode='a') as f1:
print('__iter__' in dir(f1) and '__next__' in dir(f1))
a = 'abc'
print('__iter__' in dir(a) and '__next__' in dir(a))
# 打印如下:
True # 文件句柄就是一个迭代器
False
可迭代对象转换为迭代器及取值
s1 = 'abcdefg'
obj=iter(s1)
# obj = s1.__iter__() 这样也可以
print('__iter__' in dir(s1) and '__next__' in dir(s1))
print('__iter__' in dir(obj) and '__next__' in dir(obj))
print(next(obj))
print(next(obj))
print(next(obj))
print(obj.__next__())
# 打印结果如下:
False
True
a
b
c
d
# 可以通过while循环将迭代器的值全部取出
l = [11, 22, 33, 44, 55, 66, 77, 88]
l = iter(l)
while 1:
try:
print(next(l))
except StopIteration: # except捕获到StopIteration错误后,就执行下面的break
break
可迭代对象与迭代器对比
可迭代对象:
- 是一个方法比较多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等),比较直观,但是占用内存,而且不建议直接通过循环迭代取值的这么一个数据集。
- 当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择。
迭代器:
- 是一个非常节省内存,可以记录取值位置,可以直接通过循环+next方法取值,但是不直观,操作方法比较单一的数据集。
- 当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择。(可参考为什么python把文件句柄设置成迭代器)。
生成器
获取生成器的三种方式:
- 生成器函数;
- 生成器表达式;
- python内部提供的一些。
生成器函数
# 生成器函数
def tt():
print(111)
print(222)
# 只需将定义函数时的return改为yield,就是一个生成器
yield 3
a = 10
b = 20
c = a + b
yield c
# 生成器的取值
ret = tt()
print(next(ret))
# 当只print一次时,输出结果如下:
111
222
3
# 当在上面的代码中再追加一个print时,输出结果如下:
111
222
3
30
生成器使用示例
这里以打印出5000个包子为例,可以通过列表的方式,打印出5000个包子,但这种形式是一次性把列表所有数据读到内存中的,非常占用内存,所以就可以使用生成器来完成。
# 用列表的方式打印5000个包子(非常占内存)
def func():
l1 = []
for i in range(1, 5001):
l1.append(f'{i}号包子')
return l1
for i in func():
print(i)
# 利用生成器来完成(一次只加载一个包子到内存中,节省内存)
def func():
for i in range(1, 5001):
yield (f'{i}号包子')
ret = func()
for i in range(200):
print(next(ret))
for i in range(200):
print(next(ret))
# 将会打印出1到400个包子,并且从始至终都只占一个包子的内存空间
函数中return和yield的区别
- return:函数中只存在一个return结束函数,并且给函数的执行者返回值;
- yield:只要函数中有yield关键字,那么它就是生成器函数而不是函数了,生成器函数中可以存在多个yield,一个yield对应一个next()。
yield from参数
# 直接看代码
def func():
l1=[1,2,3,4,5]
# yield from 旨在将一个可迭代对象作为一个迭代器返回
yield from l1
yield l1
ret=func()
print(next(ret))
print(next(ret))
print(next(ret))
print(next(ret))
print(next(ret))
print(next(ret))
# 打印结果如下:
1
2
3
4
5
[1, 2, 3, 4, 5]
# 也可以定义两个yield from
def func():
l1 = [1, 2, 3]
l2 = [6, 7, 8]
yield from l1
yield from l2
ret = func()
for i in ret:
print(i)
# 打印结果如下:
1
2
3
6
7
8