技术背景

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

基本使用

首先我们用一个例子来演示一下迭代生成器yield的基本使用方法,这个例子的作用是构造一个函数用于生成一个平方数组\({0^2, 1^2, 2^2 ...}\)。在普通的场景中我们一般会直接构造一个空的列表,然后将每一个计算结果填充到列表中,最后return列表即可,对应的是这里的函数square_number。而另外一个函数square_number_yield则是为了演示yield而构造的函数,其使用语法跟return是一样的,不同的是每次只会返回一个值:

  1. # test_yield.py
  2. def square_number(length):
  3. s = []
  4. for i in range(length):
  5. s.append(i ** 2)
  6. return s
  7. def square_number_yield(length):
  8. for i in range(length):
  9. yield i ** 2
  10. if __name__ == '__main__':
  11. length = 10
  12. sn1 = square_number(length)
  13. sn2 = square_number_yield(length)
  14. for i in range(length):
  15. print (sn1[i], '\t', end='')
  16. 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. # test_yield.py
  2. def square_number(length):
  3. s = []
  4. for i in range(length):
  5. s.append(i ** 2)
  6. return s
  7. def square_number_yield(length):
  8. for i in range(length):
  9. yield i ** 2
  10. if __name__ == '__main__':
  11. length = 10
  12. sn1 = square_number(length)
  13. sn2 = square_number_yield(length)
  14. sn3 = list(square_number_yield(length))
  15. for i in range(length):
  16. print (sn1[i], '\t', end='')
  17. print (next(sn2), '\t', end='')
  18. 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

进阶测试

在前面的章节中我们提到,使用yield可以节省程序的内存占用,这里我们来测试一个100000大小的随机数组的平方和计算。如果使用正常的逻辑,那么写出来的程序就是如下所示(关于python内存占用的追踪方法,可以参考这一篇博客):

  1. # square_sum.py
  2. import tracemalloc
  3. import time
  4. import numpy as np
  5. tracemalloc.start()
  6. start_time = time.time()
  7. ss_list = np.random.randn(100000)
  8. s = 0
  9. for ss in ss_list:
  10. s += ss ** 2
  11. end_time = time.time()
  12. print ('Time cost is: {}s'.format(end_time - start_time))
  13. snapshot = tracemalloc.take_snapshot()
  14. top_stats = snapshot.statistics('lineno')
  15. for stat in top_stats[:5]:
  16. print (stat)

这个程序一方面通过time来测试执行的时间,另一方面利用tracemalloc追踪程序的内存变化。这里是先用np.random.randn()直接产生了100000个随机数的数组用于计算,那么自然在计算的过程中需要存储这些生成的随机数,就会占用这么多的内存空间。如果使用yield的方法,每次只产生一个用于计算的随机数,并且按照上一个章节中的用法,这个迭代生成的随机数也是可以转化为一个完整的list的:

  1. # yield_square_sum.py
  2. import tracemalloc
  3. import time
  4. import numpy as np
  5. tracemalloc.start()
  6. start_time = time.time()
  7. def ss_list(length):
  8. for i in range(length):
  9. yield np.random.random()
  10. s = 0
  11. ss = ss_list(100000)
  12. for i in range(100000):
  13. s += next(ss) ** 2
  14. end_time = time.time()
  15. print ('Time cost is: {}s'.format(end_time - start_time))
  16. snapshot = tracemalloc.take_snapshot()
  17. top_stats = snapshot.statistics('lineno')
  18. for stat in top_stats[:5]:
  19. 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的使用方法及其应用场景。

无限长迭代器

在参考链接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. # yield_iter.py
  2. def yield_range2(i):
  3. while True:
  4. yield i
  5. i += 2
  6. iter = yield_range2(0)
  7. for i in range(10):
  8. 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/dechinphy/p/yield.html

作者ID:DechinPhy

更多原著文章请参考:https://www.cnblogs.com/dechinphy/

参考链接

  1. https://www.cnblogs.com/coder2012/p/4990834.html

python3使用迭代生成器yield减少内存占用的更多相关文章

  1. C# 处理应用程序减少内存占用

    SetProcessWorkingSetSize减少内存占用 系统启动起来以后,内存占用越来越大,使用析构函数.GC.Collect什么的也不见效果,后来查了好久,找到了个办法,就是使用 SetPro ...

  2. 简单了解一下php的迭代生成器yield

    yield是从PHP5.5开始有的,关于yidle的说明鸟哥的博客做了详细说明,我觉得是有点复杂,在看了几篇其他的帖子还有案例,我大概知道yield的作用就是在做大量数据循环处理的时候,能节省很大一部 ...

  3. PHP迭代生成器---yield

    1.迭代生成器 生成器的核心是一个yield关键字,一个生成器函数看起来像一个普通的函数,不同的是:普通函数返回一个值,而一个生成器可以yield生成许多它所需要的值.生成器函数被调用时,返回的是一个 ...

  4. 怎样使java程序减少内存占用(转载)

    本文收集网上关于减少java程序占用的一些小知识点 (1)别用new Boolean(). 在很多场景中Boolean类型是必须的,比如JDBC中boolean类型的set与get都是通过Boolea ...

  5. docker 安装 MySQL 8,并减少内存占用 记录

    目前vps 1cpu 512m内存 MySQL内存占用77% ,约350m ,经过修改配置文件优化后如下 $ ps aux 进入docker bash $ docker exec -it pwc-my ...

  6. 使用 yield 减少内存消耗

    php 里面想要处理一个文本文件,有一个方法是使用 file() 函数,但是这个函数会读取文件所有内容,可能会导致占用很大内存. // 28.1 M 的文本文件, 200w 行 $file = 'st ...

  7. SetProcessWorkingSetSize减少内存占用

    [DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")] public stat ...

  8. 使用ExpandableListView以及如何优化view的显示减少内存占用

    上篇博客讲到如何获取手机中所有歌曲的信息.本文就把上篇获取到的歌曲按照歌手名字分类.用一个ExpandableListView显示出来. MainActivity .java   public cla ...

  9. webstorm减少内存占用

    首先,按照我说的设置之后要重启才行. 在项目里找到不需要监听的文件夹右键:Mark Directory As => Cancel Exclusion 然后重启,嘿嘿,成功了!

随机推荐

  1. 第一篇文章 vim的使用

    这么长时间以来,一直没有在博客园上写过博文.那第一篇博文就以vim的使用为开端吧. 不知道有多少人还在用着ctrl+c,ctrl+v这种方式,不过,就我个人而言,还是很倾向于vim的.不管是在服务器上 ...

  2. Python 过滤字母和数字

    [前言]在写爬虫时,正则表达式有时候比较难写,一个是自己不熟练,二者数据分析提取数据千奇百怪. 一.好在python有个re模块,提供了很多更加简便的方法:可参考此文档:https://www.cnb ...

  3. 企业安全_监控Github关键字

    目录 Hawkeye GSIL Hawkeye github: https://github.com/0xbug/Hawkeye 教程: https://my.oschina.net/adailinu ...

  4. PAT-1086(Tree Traversals Again)Java语言实现+根据中序和前序遍历构建树并且给出后序遍历序列

    Tree Traversals Again Tree Traversals Again 这里的第一个tip就是注意到非递归中序遍历的过程中,进栈的顺序恰好是前序遍历的顺序,而出栈的顺序恰好是中序遍历的 ...

  5. Linux Kernel 0.12 启动简介,调试记录(Ubuntu1804, Bochs, gdb)

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...

  6. uni-app(二)接口请求封装,全局输出api

    在项目 main.js 同级创建 utils 文件夹, utils里创建 config.js文件,存储重要参数 // 获取平台信息 const { system, } = uni.getSystemI ...

  7. spring事务:事务控制方式,使用AOP控制事务,七种事务传播行为,声明事务,模板对象,模板对象原理分析

    知识点梳理 课堂讲义 1)事务回顾 1.1)什么是事务-视频01 事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败. 1.2)事务的作用 事务特征(ACID) 原子 ...

  8. beego框架panic: 'GetSecurityInf' method doesn't exist in the controller CorporateInfcontroller问题解决

    在使用beego框架时,出现类似于panic: 'GetSecurityInf' method doesn't exist in the controller CorporateInfcontroll ...

  9. Hive源码分析(1)——HiveServer2启动过程

    1.想了解HiveServer2的启动过程,则需要找到启动HiveServer2的入口,hive服务的启动命令为hive --service HiveServer2,通过分析$HIVE_HOME/bi ...

  10. POJ1562_Oil Deposits(JAVA语言)

    思路:bfs.水题,标记下计数就完了. Oil Deposits Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 22928 ...