迭代器与生成器
一、迭代器
容器
- 在python中一切都是对象,对象的抽象就是就是类,对象的集合就是容器。
可迭代的对象
- 字符串 元组 列表 字典 集合 类 构成的数据都是一种容器。容器之间的区别在于内部数据结构的实现方法
迭代器
- 迭代器是访问容器的一种方式。
- 是一个可以记住遍历的位置的对象。
- 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退
可迭代对象通过iter()函数返回一个迭代器,在通过next()函数就可以实现遍历。我们之前学习的for in 其实把这个过程隐式化了。它的原理就是这样的。
- iter() 创建迭代器
- next() 迭代一个元素
如下演示的是一个列表的迭代过程
data = [1,2,3,4]
die_dai_qi = iter(data) # 创建一个迭代器 迭代的对象是一个列表
print(next(die_dai_qi)) # 每次只迭代一个元素
print(next(die_dai_qi))
print(next(die_dai_qi))
print(next(die_dai_qi))
print(next(die_dai_qi)) # 当元素迭代完成 迭代器结束 再次迭代报错
/usr/local/bin/python3.7 /Users/zhonglinglong/PycharmProjects/whole_world/临时文件.py
Traceback (most recent call last):
File "/Users/zhonglinglong/PycharmProjects/whole_world/临时文件.py", line 18, in <module>
print(next(die_dai_qi))
StopIteration
1
2
3
4
Process finished with exit code 1
如下演示的是迭代一个类的过程
- 把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__() 与 __next__()
- 迭代类对象的过程实际上是迭代__iter__()的值,在下一次迭代时 会先执行 __next__()
class Demo:
def __iter__(self):
self.a = 1
return self
def __next__(self):
x = self.a
self.a += 1
return x
die_dai_qi = iter(Demo())
print(next(die_dai_qi))
print(next(die_dai_qi))
print(next(die_dai_qi))
print(next(die_dai_qi))
print(next(die_dai_qi))
print(next(die_dai_qi))
print(next(die_dai_qi))
二、生成器
生成器其实是一种特殊的迭代器。它的本质就是一个返回迭代器的函数 。
- (i for i in range(10000)) 小阔号阔起来的也是一个生成器用来遍历生成的数
- 同样是求和。迭代器永远都是先生成数据。然后在迭代。而生成器则是在求和的时候。要一个数字则迭代一个。然后舍弃一个。所以它的内存变化几乎为0。如果数据量太的时候。这种差距非常非常巨大。
# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024. / 1024
print('{} 程序占用内存: {} MB'.format(hint, memory))
def test_iterator():
show_memory_info('未生成迭代器之前占用的内存')
list_1 = [i for i in range(10000)]
show_memory_info('使用迭代器之后占用的内存')
print(sum(iter(list_1)))
show_memory_info('求和之后占用的内存')
def test_generator():
show_memory_info('未生成生代器之前占用的内存')
list_2 = (i for i in range(10000))
show_memory_info('生成生代器之后占用的内存')
print(sum(list_2))
show_memory_info('求和之后占用的内存')
test_iterator()
test_generator()
/usr/local/bin/python3.7 /Users/zhonglinglong/PycharmProjects/whole_world/临时文件.py
未生成迭代器之前占用的内存 程序占用内存: 47.8984375 MB
使用迭代器之后占用的内存 程序占用内存: 48.234375 MB
49995000
求和之后占用的内存 程序占用内存: 48.234375 MB
未生成生代器之前占用的内存 程序占用内存: 48.234375 MB
生成生代器之后占用的内存 程序占用内存: 48.234375 MB
49995000
求和之后占用的内存 程序占用内存: 48.234375 MB
Process finished with exit code 0
- 一个函数出出现了yield 函数那么该函数就是一个生成器。它返回的是个生成器
- 我们看下generator函数。它返回的就是一个生成器(查看打印结果) 因为有yield函数存在。它的作用简单的理解成。当调用函数generator 程序执行到yield 函数暂停 直接跳出程序。不过如果它就这个作用那就没有意义了。它从这里跳出。是为了等待next()函数来跳出。 第一次执行next(generator(1)) 的结果就是yield 后面跟着的语句值并暂停。 再次调用next函数 先执行 yield下面的语句直到再次遇到yield 返回该值然后暂停。而且generator函数里面的局部变量i 并没有因为执行过一次而清除。而是在每次next执行的时候跟着变化
def generator(k):
i = 1
while True:
yield i ** k
i += 1
gen_1 = generator(1)
gen_3 = generator(3)
print(gen_1)
print(gen_3)
def get_sum(n):
sum_1, sum_3 = 0, 0
for i in range(n):
next_1 = next(gen_1)
next_3 = next(gen_3)
print('next_1 = {}, next_3 = {}'.format(next_1, next_3))
sum_1 += next_1
sum_3 += next_3
print(sum_1 * sum_1, sum_3)
get_sum(8)
/usr/local/bin/python3.7 /Users/zhonglinglong/PycharmProjects/whole_world/临时文件.py
<generator object generator at 0x11739a7c8>
<generator object generator at 0x11739a750>
next_1 = 1, next_3 = 1
next_1 = 2, next_3 = 8
next_1 = 3, next_3 = 27
next_1 = 4, next_3 = 64
next_1 = 5, next_3 = 125
next_1 = 6, next_3 = 216
next_1 = 7, next_3 = 343
next_1 = 8, next_3 = 512
1296 1296
Process finished with exit code 0
综述对比迭代器和生成器。
- 生成器在使用数据的时候才生成。不会造成过大的内存压力
- 迭代器是一个有限集。因为在你生成数据的时候就已经指定了迭代次数。然而生成器是无限集。因为它返回的是一个迭代器的函数。你只管调next()。生成器根据运算会自动生成新的元素然后在返回给你
给一个列表,和值。求值在列表中的下标。如下演示代码通过正常写法和用生成器的写法(要注意的是。生成器返回的是一个对象。要list转化成列表)
def index_normal(L, target):
result = []
for i, num in enumerate(L):
if num == target:
result.append(i)
return result
print(index_normal([1, 6, 2, 4, 5, 2, 8, 6, 3, 2], 2))
def index_generator(L, target):
for i, num in enumerate(L): # 把可迭代的序列组合成索引和值。类似于字典的item
if num == target:
yield i # 条件满足的时候生成器追加一个元素
print(list(index_generator([1, 6, 2, 4, 5, 2, 8, 6, 3, 2], 2)))