函数和常用模块【day05】:生成器(四)
本节内容
1、概述
2、列表生成式
3、生成器
4、函数实现生成器
5、生成器表达式
一、概述
我们在使用一组数据时,通常情况下会定义一个列表,然后循环里面的元素,但是你想过没有,如果你只需要使用列表中的1-2个元素,其他的元素用不到,这样就会造成资源的浪费,这样不能很好的合理的利用我们机器的资源,那我们如何合理高效的利用这些利用这些资源,并且提高我们程序的运行速度呢?下面我们就来讲讲我们今天最关键的知识点,生成器。
二、列表生成式
1、定义
看列表[0,1,2,3,4,5,6,7,8,9],需求是把列表中的每个元素加1,你是怎么实现的呐?
1
2
3
4
5
6
7
|
a = [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] for index,i in enumerate (a): a[index] + = 1 print (a) #输出 [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] |
当然可能还有其他方法,这边就逐一介绍了,我这边有一个最简单的方法:
1
2
|
>>> [ i * 2 for i in range ( 10 )] [ 0 , 2 , 4 , 6 , 8 , 10 , 12 , 14 , 16 , 18 ] |
以上这种就叫列表生成
应用案例
===矩阵样例===
你需要迭代一个有三行五列的矩阵么? 很简单:
>>> [(x+1,y+1) for x in range(3) for y in range(5)]
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2,
3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5)]
===磁盘文件样例===
假设我们有如下这样一个数据文件 hhga.txt , 需要计算出所有非空白字符的数目:
And the Lord spake, saying, "First shalt thou take
out the Holy Pin. Then shalt thou count to three,
no more, no less. Three shall be the number thou shalt
count, and the number of the counting shall be three.
Four shalt thou not count, nei- ther count thou two,
excepting that thou then proceed to three. Five is
right out. Once the number three, being the third
number, be reached, then lobbest thou thy Holy Hand
Grenade of Antioch towards thy foe, who, being
naughty in My sight, shall snuff it."
我们已经知道可以通过 for line in data 迭代文件内容, 不过, 除了这个, 我们还可以把每
行分割( split )为单词, 然后我们可以像这样计算单词个数:
>>> f = open('hhga.txt', 'r')
>>> len([word for line in f for word in line.split()])
91
快速地计算文件大小
import os
>>> os.stat('hhga.txt').st_size
499L
假定文件中至少有一个空白字符, 我们知道文件中有少于 499 个非空字符. 我们可以把每个
单词的长度加起来, 得到和.
>>> f.seek(0)
>>> sum([len(word) for line in f for word in line.split()])
408
这里我们用 seek() 函数回到文件的开头, 因为迭代器已经访问完了文件的所有行. 一个清晰
明了的列表解析完成了之前需要许多行代码才能完成的工作! 如你所见, 列表解析支持多重嵌套
for 循环以及多个 if 子句. 完整的语法可以在官方文档中找到. 你也可以在 PEP 202 中找到更多
关于列表解析的资料.
三、生成器
正如我之前所说的,我们可以通过列表生成式,直接去创建一个列表。但是收到内存的限制,列表的容量是有限的。如果我们在创建一个包含100万个元素的列表,甚至更多,不仅占用了大量的内存空间,而且如果我们仅仅需要访问前面几个元素时,那后面很大一部分的占用的空间都白白浪费掉了。这个并不是我们所希望看到的。
所以我们就诞生了一个新的名词叫生成器:generator。下面我们就来说说这个生成器的作用。
生成器的作用:列表的元素按某种算法推算出来,我们在后续的循环中不断推算出后续的元素,在python中,这种一边循环一边计算的机制,称之为生成器(generator)。
1、创建生成器
1
2
3
4
5
6
|
>>> m = [i * 2 for i in range ( 10 )] >>> m [ 0 , 2 , 4 , 6 , 8 , 10 , 12 , 14 , 16 , 18 ] #生成一个list >>> n = (i * 2 for i in range ( 10 )) >>> n <generator object <genexpr> at 0x00000000033A4FC0 > #生成一个generator |
如果需要访问生成器n中的值,python2是通过next()方法去获得generator的下一个返回值,python3是通过__next__()去获得generator的下一个返回值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#python 3的访问方式用__next__() >>> n.__next__() 0 >>> n.__next__() 2 >>> n.__next__() 4 >>> n.__next__() 6 >>> n.__next__() 8 >>> n.__next__() #没有元素时,则会抛出抛出StopIteration的错误 Traceback (most recent call last): File "<pyshell#4>" , line 1 , in <module> n.__next__() StopIteration #python2的访问方式用next() >>> n. next () #可以用n.next() 0 >>> next (n) #也可以用next(n) 2 >>> n. next () 4 >>> n. next () 6 >>> n. next () 8 >>> n. next () #没有元素时,则会抛出抛出StopIteration的错误 Traceback (most recent call last): File "<pyshell#4>" , line 1 , in <module> n. next () StopIteration |
小结:①generator保存的是算法,每次调用next方法时,就会计算下一个元素的值,直到计算到最后一个元素,如果没有更多元素,则会抛出StopIteration的错误。
②generator只记住当前位置,它访问不到当前位置元素之前和之后的元素,之前的数据都没有了,只能往后访问元素,不能访问元素之前的元素。
2、用for循环去访问generator中的元素
用next方法去一个一个访问,仿佛有点变态,也不切实际,正确的方法是使用for循环去访问,因为generator也是可迭代对象,代码如下:
1
2
3
4
5
6
7
8
9
10
|
>>> res = (i * 2 for i in range ( 3 )) #创建一个生成器 >>> res <generator object <genexpr> at 0x0000000003155C50 > >>> for i in res: #迭代生成器中的元素 print (i) #输出 0 2 4 |
所以我们在创建一个生成器以后,基本不会用next方法去访问,而是通过for循环来迭代它,并且更不用关心StopIteration错误。
四、函数实现生成器
上面推算比较简单,但是推算的算法比较复杂,用类似列表生成式的for循环无法实现,那怎么办呢?比如下面一个例子,用列表生成式无法实现。
1、斐波那契数列
实现原理:除第一个和第二个数外,任意一个数都可由前两个数相加得到:1, 1, 2, 3, 5, 8, 13, 21, 34, ...,代码如下:
1
2
3
4
5
6
7
8
|
def fib( max ): n,a,b = 0 , 0 , 1 while n < max : print (b) a , b = b ,a + b n = n + 1 return "----done---" |
很明显斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易。
这边需要注意的是赋值语句:
1
|
a , b = b ,a + b |
相当于:
1
2
3
|
t = (b, a + b) # t是一个tuple a = t[ 0 ] b = t[ 1 ] |
上面的执行结果:
1
2
3
4
5
6
7
8
|
fib( 5 ) #执行结果 1 1 2 3 5 - - - - done - - - |
根据这种逻辑推算非常类似一个生成器(generator)。但是怎么把一个函数转换成一个生成器呢?
2、用yield函数转换为生成器(generator)
1
2
3
4
5
6
7
8
|
def fib( max ): n,a,b = 0 , 0 , 1 while n < max : yield b #用yield替换print,把fib函数转化成一个生成器 a , b = b ,a + b n = n + 1 return "----done---" |
以上就是生成器(generator)另外一种定义方法。如果一个函数中包含yield关键字,那么这个函数就不是一个普通的函数,而是一个生成器(generator)。
1
2
3
4
5
|
f = fib( 5 ) print (f) #输出 <generator object fib at 0x0000000000D1B4C0 > |
这边有两个难理解地方:①函数是顺序执行的,遇到return
语句或者最后一行函数语句就返回
②变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
①访问元素:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
f = fib( 5 ) print (f.__next__()) print (f.__next__()) print (f.__next__()) print ( "我在干别的事情" ) print (f.__next__()) print (f.__next__()) #访问的是最后一个元素 print (f.__next__()) #没有多余的元素 #输出 1 1 2 - - - - - 我在干别的事情 - - - - - 3 5 Traceback (most recent call last): File "D:/PycharmProjects/pyhomework/day4/生成器/fib.py" , line 20 , in <module> print (f.__next__()) StopIteration: - - - - done - - - |
从上面的例子可以看出来:①我访问生成器中的元素,不用是连续的,我可以中间去执行其他程序,向想什么时候执行,可以再回头去执行。
②return在这边作用就是当发生异常时,会打印ruturn后面的值。
②for循环访问
1
2
3
4
5
6
7
8
9
10
|
f = fib( 5 ) for i in f: print (i) #输出 1 1 2 3 5 |
③捕获这个StopIteration这个异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
f = fib( 5 ) while True : try : x = f.__next__() print ( "f:" ,x) except StopIteration as e: #当try中的程序执行错误了,才会执行except下面的代码 print ( "Generator return value:" ,e.value) break #执行结果 f: 1 f: 1 f: 2 f: 3 f: 5 Generator return value: - - - - done - - - |
关于这个异常处理,后续会继续发博客更新,请大家敬请期待。。。。。
五、生成器表达式
===磁盘文件样例===
在前边列表解析一节, 我们计算文本文件中非空白字符总和. 最后的代码中, 我们展示了如何
使用一行列表解析代码做所有的事. 如果这个文件的大小变得很大, 那么这行代码的内存性能会很
低, 因为我们要创建一个很长的列表用于存放单词的长度.
为了避免创建庞大的列表, 我们可以使用生成器表达式来完成求和操作. 它会计算每个单词的
长度然后传递给 sum() 函数(它的参数不仅可以是列表,还可以是可迭代对象,比如生成器表达式).
这样, 我们可以得到优化后的代码(代码长度, 还有执行效率都很高效):
>>> sum(len(word) for line in data for word in line.split())
408
我们所做的只是把方括号删除: 少了两字节, 而且更节省内存 ... 非常地环保!
=== 交叉配对例子 ===
生成器表达式就好像是懒惰的列表解析(这反而成了它主要的优势). 它还可以用来处理其他列
表或生成器, 例如这里的 rows 和 cols :
rows = [1, 2, 3, 17]
def cols(): # example of simple generator
yield 56
yield 2
yield 1
不需要创建新的列表, 直接就可以创建配对. 我们可以使用下面的生成器表达式:
x_product_pairs = ((i, j) for i in rows for j in cols())
现在我们可以循环 x_product_pairs , 它会懒惰地循环 rows 和 cols :
>>> for pair in x_product_pairs:
... print pair
...
(1, 56)
(1, 2)
(1, 1)
(2, 56)
(2, 2)
(2, 1)
(3, 56)
(3, 2)
(3, 1)
(17, 56)
(17, 2)
(17, 1)
=== 重构样例 ===
我们通过一个寻找文件最长的行的例子来看看如何改进代码. 在以前, 我们这样读取文件:
f = open('/etc/motd', 'r')
longest = 0
while True:
linelen = len(f.readline().strip()) if not linelen:
break
if linelen > longest:
longest = linelen
f.close()
return longest
事实上, 这还不够老. 真正的旧版本 Python 代码中, 布尔常量应该写是整数 1 , 而且我们应
该使用 string 模块而不是字符串的 strip() 方法:
import string
:
len(string.strip(f.readline()))
从那时起, 我们认识到如果读取了所有的行, 那么应该尽早释放文件资源. 如果这是一个很多
进程都要用到的日志文件, 那么理所当然我们不能一直拿着它的句柄不释放. 是的, 我们的例子是
用来展示的, 但是你应该得到这个理念. 所以读取文件的行的首选方法应该是这样:
f = open('/etc/motd', 'r')
longest = 0
allLines = f.readlines()
f.close()
for line in allLines:
linelen = len(line.strip())
if linelen > longest:
longest = linelen
return longest
列表解析允许我们稍微简化我们代码, 而且我们可以在得到行的集合前做一定的处理. 在下段
代码中, 除了读取文件中的行之外,我们还调用了字符串的 strip() 方法处理行内容.
f = open('/etc/motd', 'r')
longest = 0
allLines = [x.strip() for x in f.readlines()]
f.close()
for line in allLines:
linelen = len(line)
if linelen > longest:
longest = linelen
return longest
Edit By Vheavens
Edit By Vheavens
然而, 两个例子在处理大文件时候都有问题, 因为 readlines() 会读取文件的所有行. 后来
我们有了迭代器, 文件本身就成为了它自己的迭代器, 不需要调用 readlines() 函数. 我们已经
做到了这一步, 为什么不去直接获得行长度的集合呢(之前我们得到的是行的集合)? 这样, 我们就
可以使用 max() 内建函数得到最长的字符串长度:
f = open('/etc/motd', 'r')
allLineLens = [len(x.strip()) for x in f]
f.close()
return max(allLineLens)
这里唯一的问题就是你一行一行迭代 f 的时候, 列表解析需要文件的所有行读取到内存中,
然后生成列表. 我们可以进一步简化代码: 使用生成器表达式替换列表解析, 然后把它移到 max()
函数里, 这样, 所有的核心部分只有一行:
f = open('/etc/motd', 'r')
longest = max(len(x.strip()) for x in f)
f.close()
return longest
最后, 我们可以去掉文件打开模式(默认为读取), 然后让 Python 去处理打开的文件. 当然,
文件用于写入的时候不能这么做, 但这里我们不需要考虑太多:
return max(len(x.strip()) for x in open('/etc/motd'))
我们走了好长一段路. 注意,即便是这只有一行的 Python 程序也不是很晦涩. 生成器表达式
在 Python 2.4 中被加入, 你可以在 PEP 289 中找到更多相关内容.
函数和常用模块【day05】:生成器(四)的更多相关文章
- 函数和常用模块【day05】:生成器并行计算(五)
本节内容 1.概述 2.生成器执行原理 3.send()和__next__()方法的区别 4.yield实现并行效果 一.概述 之前只是介绍生成器,那有些同学就说了,这个生成器除了能节省资源,提高工作 ...
- 函数和常用模块【day05】:迭代器(六)
本节内容 1.简书 2.可迭代对象 3.迭代器 4.rang方法 5.总结 一.简述 我们经常使用for循环去遍历一些序列数据,但是我们有的时间发现for循环的效率很低,而且很占用了大量的硬件资源,但 ...
- 函数和常用模块【day05】:不同目录间进行模块调用(八)
本节内容 1.背景 2.函数功能解释 3.绝对路径和相对路径 4.不同目录间进行模块调用 一.背景 之前写了软件开发目录规范这篇博客,相信很多人都已经知道,我们在写程序时需要遵循一定的规范,不然,就算 ...
- 函数和常用模块【day05】:装饰器高潮(三)
本节内容 1.概述 2.装饰器定义 3.装饰器定义 4.带参数的生成器 一.概述 我们之前介绍了大幅片的内容,感觉跟装饰器半毛钱关系都没有,其实不然,我们分别详细阐述了高阶函数和内置函数,下面我们就来 ...
- 函数和常用模块【day06】:shutil模块(四)
本节内容 简书 模块详解 压缩解压 一.简述 我们在日常处理文件时,经常用到os模块,但是有的时候你会发现,像拷贝.删除.打包.压缩等文件操作,在os模块中没有对应的函数去操作,下面我们就来讲讲高级的 ...
- 函数和常用模块【day05】:文件目录开发规范(七)
本节内容 1.背景 2.设计目录结构的好处 3.关于readme的内容 4.关于requirements.txt和setup.py 5.关于配置文件的使用方法 一.背景 "设计项目目录结构& ...
- 函数和常用模块【day04】:作用域、局部和全局变量(四)
本节内容 概述 课前前引 局部变量和全局变量 总结 一.概述 我们之前写代码,都需要声明变量,但是我们思考过变量的作用范围吗?今天我们就来讲讲变量的作用范围,这个作用范围又叫作用域.首先我们根据变量的 ...
- 函数和常用模块【day05】:装饰器前奏(一)
本节内容 定义 原则 实现装饰器的储备知识 函数及变量 高阶函数 一.定义 1.装饰器:本质是函数. 2.功能:用来装饰其他函数,顾名思义就是,为其他的函数添加附件功能的. 二.原则 不能修改被装饰函 ...
- 函数和常用模块【day05】:装饰器前戏(二)
本节内容 嵌套函数 局部作用域和全局作用域的访问顺序 一.嵌套函数 1.定义 在一个函数的函数体内,用def 去声明一个函数,而不是去调用其他函数,称为嵌套函数. 1 2 3 4 5 6 7 8 9 ...
随机推荐
- 软件工程实践作业2 --梭哈游戏(java) 实践报告
一,题目简介: 1.创建一副扑克牌 7------k 加入到集合对象中2.对扑克牌洗牌3.定义参与游戏的玩家的人,通过键盘输入,限定人数2-54.人数符合要求继续执行,不符合退出5.对玩家发牌,每个人 ...
- org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure The last packet sent succ
数据库 没有开启 连接失败 org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause ...
- PAT 甲级 1134 Vertex Cover
https://pintia.cn/problem-sets/994805342720868352/problems/994805346428633088 A vertex cover of a gr ...
- 求两个整数的最大公约数GCM
思路分析: (1)求差判定法: 如果两个数相差不大,可以用大数减去小数,所得的差与小数的最大公约数就是原来两个数的最大公约数.例如:求78和60的最大公约数.78-60=18,18和60的最大公约数 ...
- Docker(二十七)-Docker 清理占用的磁盘空间
1. docker system命令 docker system df命令,类似于Linux上的df命令,用于查看Docker的磁盘使用情况: docker system dfTYPE TOTAL A ...
- 请求与响应编码及jsp基本原理
1.请求转发和请求包含 (1)请求转发: this.getServletContext().getRequestDispatcher("").forward(request,res ...
- spring学习总结(一)_Ioc基础(上)
最近经历了许许多多的事情,学习荒废了很久.自己的目标成了摆设.现在要奋起直追了.最近发现了张果的博客.应该是一个教师.看了他写的spring系列的博客,写的不错.于是本文的内容参考自他的博客,当然都是 ...
- 使用nmon进行系统监控
一.下载并安装: 下载地址:http://nmon.sourceforge.net/pmwiki.php?n=Site.Download 下载版本:nmon16g_x86.tar.gz 不用的Li ...
- BZOJ3028 食物(生成函数)
显然构造出生成函数:则有f(x)=(1+x2+x4+……)·(1+x)·(1+x+x2)·(x+x3+x5+……)·(1+x4+x8+……)·(1+x+x2+x3)·(1+x)·(1+x3+x6+…… ...
- SQL partition (小组排序)
很多时候,我们在SQL中进行数据去重(distinct) 结果发现有2条一样ID,或者name的数据,我们想要最接近的那条数据. 直接看看题目: 原表 select ID,Title,PRICE fr ...