翼度科技»论坛 编程开发 python 查看内容

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

2

主题

2

帖子

6

积分

新手上路

Rank: 1

积分
6
在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的优势,也就是在计算过程中每次只占用一个元素的内存,而不需要一直存储大量的元素在内存中。

来源:https://www.cnblogs.com/Pythonmiss/p/18164372
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具