Python 进阶_生成器 & 生成器表达式
目录
相关知识点
生成器
带有 yield 关键字的的函数在 Python 中被称之为 generator(生成器)。Python 解释器会将带有 yield 关键字的函数视为一个 generator 来处理。一个函数或者子程序都只能 return 一次,但是一个生成器能暂停执行并返回一个中间的结果 —— 这就是 yield 语句的功能 : 返回一个中间值给调用者并暂停执行。
EXAMPLE:
In [94]: def fab(max):
...: n, a, b = 0, 0, 1
...: while n < max:
...: yield b
...: a, b = b, a + b
...: n = n + 1
...:
In [95]: f = fab(5)
In [96]: f.next()
Out[96]: 1
In [97]: f.next()
Out[97]: 1
In [98]: f.next()
Out[98]: 2
In [99]: f.next()
Out[99]: 3
In [100]: f.next()
Out[100]: 5
In [101]: f.next()
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-101-c3e65e5362fb> in <module>()
----> 1 f.next()
StopIteration:
生成器 fab()
的执行过程
执行语句 f = fab(5)
时,并不会马上执行 fab()
函数的代码块,而是首先返回一个 iterable 对象!
在 for 循环语句执行时,才会执行 fab()
函数的代码块。
执行到语句 yield b
时,fab()
函数会返回一个迭代值,直到下次迭代前,程序流会回到 yield b
的下一条语句继续执行,然后再次回到 for 循环,如此迭代直到结束。看起来就好像一个函数在正常执行的过程中被 yield
中断了数次,每次中断都会通过 yield
返回当前的迭代值。
由此可以看出,生成器通过关键字 yield
不断的将迭代器返回到内存进行处理,而不会一次性的将对象全部放入内存,从而节省内存空间。从这点看来生成器和迭代器非常相似,但如果更深入的了解的话,其实两者仍存在区别。
生成器和迭代器的区别
生成器的另一个优点就是它不要求你事先准备好整个迭代过程中所有的元素,即无须将对象的所有元素都存入内存之后,才开始进行操作。生成器仅在迭代至某个元素时才会将该元素放入内存,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的类序列对象,EG. 大文件/大集合/大字典/斐波那契数列等。这个特点被称为 延迟计算 或 惰性求值(Lazy evaluation),可以有效的节省内存。惰性求值实际上是现实了协同程序 的思想。
协同程序:是一个可以独立运行的函数调用,该调用可以被暂停或者挂起,之后还能够从程序流挂起的地方继续或重新开始。当协同程序被挂起时,Python 就能够从该协同程序中获取一个处于中间状态的属性的返回值(由 yield 返回),当调用 next()
方法使得程序流回到协同程序中时,能够为其传入额外的或者是被改变了的参数,并且从上次挂起的下一条语句继续执行。这是一种类似于进程中断的函数调用方式。这种挂起函数调用并在返回属性中间值后,仍然能够多次继续执行的协同程序被称之为生成器。
NOTE:而迭代器是不具有上述的特性的,不适合去处理一些巨大的类序列对象,所以建议优先考虑使用生成器来处理迭代的场景。
生成器的优势
综上所述:使用生成器最好的场景就是当你需要以迭代的方式去穿越一个巨大的数据集合。比如:一个巨大的文件/一个复杂的数据库查询等。
EXAMPLE 2:读取一个大文件
def read_file(fpath):
BLOCK_SIZE = 1024
with open(fpath, 'rb') as f:
while True:
block = f.read(BLOCK_SIZE)
if block:
yield block
else:
return
如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件的部分内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。
加强的生成器特性
除了可以使用 next()
方法来获取下一个生成的值,用户还可以使用 send()
方法将一个新的或者是被修改的值返回给生成器。除此之外,还可以使用 close()
方法来随时退出生成器。
EXAMPLE 3:
In [5]: def counter(start_at=0):
...: count = start_at
...: while True:
...: val = (yield count)
...: if val is not None:
...: count = val
...: else:
...: count += 1
...:
In [6]: count = counter(5)
In [7]: type(count)
Out[7]: generator
In [8]: count.next()
Out[8]: 5
In [9]: count.next()
Out[9]: 6
In [10]: count.send(9) # 返回一个新的值给生成器中的 yield count
Out[10]: 9
In [11]: count.next()
Out[11]: 10
In [12]: count.close() # 关闭一个生成器
In [13]: count.next()
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-13-3963aa0a181a> in <module>()
----> 1 count.next()
StopIteration:
生成器表达式
生成器表达式是列表解析的扩展,就如上文所述:生成器是一个特定的函数,允许返回一个中间值,然后挂起代码的执行,稍后再恢复执行。列表解析的不足在于,它必须一次性生成所有的数据,用以创建列表对象,所以不适用于迭代大量的数据。
生成器表达式通过结合列表解析和生成器来解决这个问题。
列表解析
[expr for iter_var in iterable if cond_expr]
生成器表达式
(expr for iter_var in iterable if cond_expr)
两者的语法非常相似,但生成器表达式返回的不是一个列表类型对象,而是一个生成器对象,生成器是一个内存使用友好的结构。
生成器表达式样例
通过改进查找文件中最长的行的功能实现来看看生成器的优势。
EXAMPLE 4 : 一个比较通常的方法,通过循环将更长的行赋值给变量 longest 。
f = open('FILENAME', 'r')
longest = 0
while True:
linelen = len(f.readline().strip())
if not linelen:
break
if linelen > longest:
longest = linelen
f.close()
return longest
很明显的,在这里例子中,需要迭代的对象是一个文件对象。
改进 1:
需要注意的是,如果我们读取一个文件所有的行,那么我们应该尽早的去释放这个文件资源。例如:一个日志文件,会有很多不同的进程会其进行操作,所以我们不能容忍任意一个进程拿着这个文件的句柄不放。
f = open('FILENAME', 'r')
longest = 0
allLines = f.readlines()
f.close()
for line in allLines:
linelen = len(line.strip())
if not linelen:
break
if linelen > longest:
longest = linelen
return longest
改进 2:
我们可以使用列表解析来简化上述的代码,例如:在得到 allLines 所有行的列表时对每一行都进行处理。
f = open('FILENAME', 'r')
longest = 0
allLines = [x.strip() for x in f.readlines()]
f.close()
for line in allLines:
linelen = len(line)
if not linelen:
break
if linelen > longest:
longest = linelen
return longest
改进 3:
当我们处理一个巨大的文件时,file.readlines()
并不是一个明智的选择,因为 readlines()
会读取文件中所有的行。那么我们是否有别的方法来获取所有行的列表呢?我们可以应用 file 文件内置的迭代器。
f = open('FILENAME', 'r')
allLinesLen = [line(x.strip()) for x in f]
f.close()
return max(allLinesLen) # 返回列表中最大的数值
不再需要使用循环比较并保留当前最大值的方法来处理,将所有行的长度最后元素存放在列表对象中,再获取做大的值即可。
改进 4:
这里仍然存在一个问题,就是使用列表解析来处理 file 对象时,会将 file 所有的行都读取到内存中,然后再创建一个新的列表对象,这是一个内存不友好的实现方式。那么,我们就可以使用生成器表达式来替代列表解析。
f = open('FILENAME', 'r')
allLinesLen = (line(x.strip()) for x in f) # 这里的 x 相当于 yield x
f.close()
return max(allLinesLen)
因为如果在函数中使用生成器表达式作为参数时,我们可以忽略括号 ‘()’,所以还能够进一步简化代码:
f = open('FILENAME', 'r')
longest = max(line(x.strip()) for x in f)
f.close()
return longest
最后:我们能够以一行代码实现这个功能,让 Python 解析器去处理打开的文件。
当然并不是说代码越少就越好,例如下面这一行代码每循环一次就会调用一个 open()
函数,效率上并没有 改进 4 更高。
return max(line(x.strip()) for x in open('FILENAME'))
小结
在需要迭代穿越一个对象时,我们应该优先考虑使用生成器替代迭代器,使用生成器表达式替代列表解析。当然这并不是绝对的。 迭代器和生成器是 Python 很重要的特性,对其有很好的理解能够写出更加 Pythonic 的代码。
Python 进阶_生成器 & 生成器表达式的更多相关文章
- python进阶_浅谈面向对象进阶
python进阶_浅谈面向对象进阶 学了面向对象三大特性继承,多态,封装.今天我们看看面向对象的一些进阶内容,反射和一些类的内置函数. 一.isinstance和issubclass class F ...
- python语法_列表生成器_生成器_迭代器_异常捕获
列表生成式 a = [x for x in range(10)] print(a) x 可进行操作 a = [x*2 for x in range(10)] print(a) x甚至可以为函数, de ...
- Python进阶_类与实例
上一节将到面对对象必须先抽象模型,之后直接利用模型.这一节我们来具体理解一下这句话的意思. 面对对象最重要的概念就是类(class)和实例(instance),必须牢记类是抽象的模板,比如studen ...
- Python 进阶_模块 & 包
目录 目录 模块的搜索路径和路径搜索 搜索路径 命名空间和变量作用域的比较 变量名的查找覆盖 导入模块 import 语句 from-import 语句 扩展的 import 语句 as 自动载入模块 ...
- Python 进阶_函数式编程
目录 目录 函数式编程 Python 函数式编程的特点 高阶函数 匿名函数 lambda 函数式编程相关的内置函数 filter 序列对象过滤器 map reduce 折叠 自定义的排序函数 最后 函 ...
- Python 进阶_迭代器 & 列表解析
目录 目录 迭代器 iter 内建的迭代器生成函数 迭代器在 for 循环中 迭代器与字典 迭代器与文件 创建迭代器对象 创建迭代对象并实现委托迭代 迭代器的多次迭代 列表解析 列表解析的样例 列表解 ...
- Python 进阶_闭包 & 装饰器
目录 目录 闭包 函数的实质和属性 闭包有什么好处 小结 装饰器 更加深入的看看装饰器的执行过程 带参数的装饰器 装饰器的叠加 小结 装饰器能解决什么问题 小结 闭包 Closure: 如果内层函数引 ...
- Python进阶_面对对象&面对过程
这节主要讲面对对象与面对过程两种编程思想的主要区别. 一. 简单对比 面向过程是一种基础的方法,它考虑的是实际的实现步骤,一般情况下,面向过程是自顶向下逐步求精,其最重要的是模块化的思想方法. 面向对 ...
- 【Python】【容器 | 迭代对象 | 迭代器 | 生成器 | 生成器表达式 | 协程 | 期物 | 任务】
Python 的 asyncio 类似于 C++ 的 Boost.Asio. 所谓「异步 IO」,就是你发起一个 IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知. Asyn ...
随机推荐
- IDF-CTF-cookie欺骗 writeup
题目链接: http://ctf.idf.cn/index.php?g=game&m=article&a=index&id=40 知识点:base64解码, cookie欺骗 ...
- luoguP2590 [ZJOI2008]树的统计(树链剖分)
luogu P2590 [ZJOI2008]树的统计 题目 #include<iostream> #include<cstdlib> #include<cstdio> ...
- shell 脚本规范
shell 脚本规范 一.背景 1.使用哪一种shell? 必须使用bash shell 2.什么时候使用shell? 数量相对较少的操作 脚本文件少于100行 3.脚本文件扩展名是什么? shell ...
- Ubuntu系统安装配置tensorflow开发环境
Ubuntu系统安装 下载ubuntu iso 选择目前最新的版本是 Ubuntu 18.04 LTS .下载地址: 官网:https://www.ubuntu.com/download/deskto ...
- Solr的学习使用之(三)IKAnalyzer中文分词器的配置
1.为什么要配置? 1.我们知道要使用Solr进行搜索,肯定要对词语进行分词,但是由于Solr的analysis包并没有带支持中文的包或者对中文的分词效果不好,需要自己添加中文分词器:目前呼声较高的是 ...
- Pjsip Porting to Hisilicon SOC
1)HiSilicon Compiler arm-himix100-linux.tgz or arm-himix100-linux.tgz #Installation instructions are ...
- 2018-4-30-win2d-CanvasRenderTarget-vs-CanvasBitmap
title author date CreateTime categories win2d CanvasRenderTarget vs CanvasBitmap lindexi 2018-04-30 ...
- 基于TI 多核DSP 的大数据智能计算处理解决方案
北京太速科技有限公司 大数据智能计算,是未来的一个发展趋势,大数据计算系统主要完成数据的存储和管理:数据的检索与智能计算. 特别是在智能城市领域,由于人口聚集给城市带来了交通.医疗.建筑等各方面的压力 ...
- 175-基于TI DSP TMS320C6455、Xilinx V5 FPGA XC5VSX95T的高速数据处理核心板
基于TI DSP TMS320C6455.Xilinx V5 FPGA XC5VSX95T的高速数据处理核心板 一.板卡概述 该DSP+FPGA高速信号采集处理板由我公司自主研发,包含一片TI DSP ...
- 六 BASH 高级变量
高级变量分为三类 变量扩展 ${变量名} 例 ${filename} 大括号 命令替换 $(命令) $(ls /) 小括号 算术扩展 $((算数式)) $(( ...