python利用迭代生成器yield减少内存占用的方法

打印 上一主题 下一主题

主题 892|帖子 892|积分 2676

在python编码中for循环处理使命时,会将全部的待遍历参量加载到内存中。
其实这本没有须要,因为这些参量很有可能是一次性利用的,甚至许多场景下这些参量是不需要同时存储在内存中的,这时候就会用到本文所介绍的迭代生成器yield。
1.基本利用

起首我们用一个例子来演示一下迭代生成器yield的基本利用方法,这个例子的作用是构造一个函数用于生成一个平方数组02,12,22...。
在普通的场景中我们一样平常会直接构造一个空的列表,然后将每一个计算结果填充到列表中,最后return列表即可,对应的是这里的函数square_number。
而另外一个函数square_number_yield则是为了演示yield而构造的函数,其利用语法跟return是一样的,不同的是每次只会返回一个值:
  1. def square_number(length):
  2.     s = []
  3.     for i in range(length):
  4.         s.append(i ** 2)
  5.     return s
  6. def square_number_yield(length):
  7.     for i in range(length):
  8.         yield i ** 2
  9. if __name__ == '__main__':
  10.     length = 10
  11.     sn1 = square_number(length)
  12.     sn2 = square_number_yield(length)
  13.     for i in range(length):
  14.         print (sn1[i], '\t', end='')
  15.         print (next(sn2))
复制代码
在main函数中我们对比了两种方法执行的结果,打印在同一行上面,用end=''指令可以替代行末的换行符号,具体执行的结果如下所示:
  1. [dechin@dechin-manjaro yield]$ python3 test_yield.py
  2. 0       0
  3. 1       1
  4. 4       4
  5. 9       9
  6. 16      16
  7. 25      25
  8. 36      36
  9. 49      49
  10. 64      64
  11. 81      81
复制代码
可以看到两种方法打印出来的结果是一样的。也许有些场景下就是需要持久化的存储函数中返回的结果,这一点用yield也是可以实现的,可以参考如下示例:
  1. def square_number(length):
  2.     s = []
  3.     for i in range(length):
  4.         s.append(i ** 2)
  5.     return s
  6. def square_number_yield(length):
  7.     for i in range(length):
  8.         yield i ** 2
  9. if __name__ == '__main__':
  10.     length = 10
  11.     sn1 = square_number(length)
  12.     sn2 = square_number_yield(length)
  13.     sn3 = list(square_number_yield(length))
  14.     for i in range(length):
  15.         print (sn1[i], '\t', end='')
  16.         print (next(sn2), '\t', end='')
  17.         print (sn3[i])
复制代码
这里利用的方法是直接将yield生成的对象转化成list格式,或者用sn3 = [i for i in square_number_yield(length)]这种写法也是可以的,在性能上应该差别不大。上述代码的执行结果如下:
  1. [dechin@dechin-manjaro yield]$ python3 test_yield.py
  2. 0       0       0
  3. 1       1       1
  4. 4       4       4
  5. 9       9       9
  6. 16      16      16
  7. 25      25      25
  8. 36      36      36
  9. 49      49      49
  10. 64      64      64
  11. 81      81      81
复制代码
2.进阶测试

在前面的章节中我们提到,利用yield可以节省步伐的内存占用,这里我们来测试一个100000巨细的随机数组的平方和计算。如果利用正常的逻辑,那么写出来的步伐就是如下所示:
  1. import tracemalloc
  2. import time
  3. import numpy as np
  4. tracemalloc.start()
  5. start_time = time.time()
  6. ss_list = np.random.randn(100000)
  7. s = 0
  8. for ss in ss_list:
  9.     s += ss ** 2
  10. end_time = time.time()
  11. print ('Time cost is: {}s'.format(end_time - start_time))
  12. snapshot = tracemalloc.take_snapshot()
  13. top_stats = snapshot.statistics('lineno')
  14. for stat in top_stats[:5]:
  15.     print (stat)
复制代码
这个步伐一方面通过time来测试执行的时间,另一方面利用tracemalloc追踪步伐的内存变化。
这里是先用np.random.randn()直接产生了100000个随机数的数组用于计算,那么自然在计算的过程中需要存储这些生成的随机数,就会占用这么多的内存空间。
如果利用yield的方法,每次只产生一个用于计算的随机数,并且按照上一个章节中的用法,这个迭代生成的随机数也是可以转化为一个完整的list的:
  1. import tracemalloc
  2. import time
  3. import numpy as np
  4. tracemalloc.start()
  5. start_time = time.time()
  6. def ss_list(length):
  7.     for i in range(length):
  8.         yield np.random.random()
  9. s = 0
  10. ss = ss_list(100000)
  11. for i in range(100000):
  12.     s += next(ss) ** 2
  13. end_time = time.time()
  14. print ('Time cost is: {}s'.format(end_time - start_time))
  15. snapshot = tracemalloc.take_snapshot()
  16. top_stats = snapshot.statistics('lineno')
  17. for stat in top_stats[:5]:
  18.     print (stat)
复制代码
这两个示例的执行结果如下,可以放在一起进行对比:
  1. [dechin@dechin-manjaro yield]$ python3 square_sum.py
  2. Time cost is: 0.24723434448242188s
  3. square_sum.py:9: size=781 KiB, count=2, average=391 KiB
  4. square_sum.py:12: size=24 B, count=1, average=24 B
  5. square_sum.py:11: size=24 B, count=1, average=24 B
  6. [dechin@dechin-manjaro yield]$ python3 yield_square_sum.py
  7. Time cost is: 0.23023390769958496s
  8. yield_square_sum.py:9: size=136 B, count=1, average=136 B
  9. yield_square_sum.py:14: size=112 B, count=1, average=112 B
  10. yield_square_sum.py:11: size=79 B, count=2, average=40 B
  11. yield_square_sum.py:10: size=76 B, count=2, average=38 B
  12. yield_square_sum.py:15: size=28 B, count=1, average=28 B
复制代码
颠末比力我们发现,两种方法的计算时间是险些差不多的,但是在内存占用上yield有着明显的优势。当然,也许这个例子并不是非常的恰当,但是本文重要还是介绍yield的利用方法及其应用场景。
3.无限长迭代器

在参考链接1中提到了一种用法是无限长的迭代器,比如按顺序返回全部的素数,那么此时我们如果用return来返回全部的元素并存储到一个列表里面,就是一个非常不经济的办法,所以可以利用yield来迭代生成,参考链接1中的源代码如下所示:
  1. def get_primes(number):
  2.     while True:
  3.         if is_prime(number):
  4.             yield number
  5.         number += 1
复制代码
那么类似的,这里我们用while True可以展示一个简朴的案例——返回全部的偶数:
  1. def yield_range2(i):
  2.     while True:
  3.         yield i
  4.         i += 2
  5.         
  6. #学习中遇到问题没人解答?小编创建了一个Python学习交流群:153708845
  7. iter = yield_range2(0)
  8. for i in range(10):
  9.     print (next(iter))
复制代码
因为这里我们限定了长度是10,所以终极会返回10个偶数:
  1. [dechin@dechin-manjaro yield]$ python3 yield_iter.py
  2. 0
  3. 2
  4. 4
  5. 6
  6. 8
  7. 10
  8. 12
  9. 14
  10. 16
  11. 18
复制代码
总结

本文介绍了python的迭代器yield,其实关于yield,我们可以简朴的将其明白为单个元素的return。
如许不仅就初步明白了yield的利用语法,也可以或许大概了解到yield的优势,也就是在计算过程中每次只占用一个元素的内存,而不需要不停存储大量的元素在内存中。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

老婆出轨

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表