初学Python——列表生成式、生成器和迭代器
一、列表生成式
假如现在有这样一个需求:快速生成一个列表[1,2,3,4,5,6,7,8,9,10],该如何实现?
在不知道列表生成式的情况下,可能会这样写:
a=[1,2,3,4,5,6,7,8,9,10]
如果要每个值+1呢?可能会这样:
for index,i in enumerate(a): a[index] +=1 print(a)
不够方便,这里讲一个快速生成列表的方法:列表生成式。意思就是立即生成列表。
生成一个1到10的列表:
a = [i+1 for i in range(10)] print( a) # output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
生成一个2~20的偶数列表:
a=[ i*2 for i in rang(1,11)] print(a) # output: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
它相当于:
a=[] for i in range(1,11): #列表生成式 a.append(i*2) print(a)
生成的列表已经存在在内存中。
二、生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的列表,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个生成器,只需要把列表生成式中的 [ ] 改成 ( ) 即可。
b=[i*2 for i in rang(10)] # 列表生成式 print(b) c=( i*2 for i in range(10) ) #生成器 print(c) # output: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] <generator object <genexpr> at 0x000001D0089B45C8>
输出c,得到的是数据类型说明和它的内存地址。
生成器只是名义上生成一个列表,但实际上却没有占用那么大内存,生成器只有调用的时候才会生成相应的数据。
如果要打印生成器的数据,则需要.__next__()方法
print(c.__next__()) # 输出第一个数 0 print(c.__next__()) # 输出第二个数 1 print(c.__next__()) # 输出第三个数 2 print(c.__next__()) # 输出第四个数 3 print(c.__next__()) # 输出第五个数 4
如果我只需要当中的最后一个数据呢?能不能直接输出?
抱歉,不能。而且,生成器的数据只能从前往后去访问,不能从后往前去访问,在内存中只保留一个值,也就是说,访问过的数据已经无法再次访问。
如果生成器有很多的数据,要全部输出,有没有简便的写法?
抱歉,没有,您只能一个一个地输出。
当然,像上面那样不断调用.__next__()还是太坑爹了,可以用for去迭代它(生成器也是可迭代对象):
g = (x * x for x in range(10)) for n in g: print(n)
那,,我还要生成器有卵用??
还是有点卵用的,生成器一般依托于函数实现,比如,我先定义一个函数fib(),函数内定义了数列的推算规则
def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done' # 注释: a, b = b, a + b 相当于: t = (b, a + b) # t是一个tuple a = t[0] b = t[1] 它不必写出显式变量 t
如果给fib()传参10,它将输出一连串的数字,可以组成一个数列:
1,1,2,3,5,8,13,21,34,55
此时的fib函数,已经非常接近生成器了,只需要一个yield即可,
def fib(max): #当函数中有yield出现时,不能将其简单视为函数,是一个生成器。 "生成器" n,a,b=0,0,1 while n<max: yield b #yield保存了函数当前的中断状态,返回当前b的值 a,b=b,a+b n=n+1 #计数器 return "done"
此时,fib(10)是一个生成器,
f = fib(6) print(f) <generator object fib at 0x104feaaa0>
这里最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
来一个一个地输出它的值:
f=fib(10) print(f) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__())
因为只能一个一个地输出,且不能得知长度,所以总会有越界的时候,会报一个异常StopIteration,导致程序停止
所以需要捕获异常:
while 1: try: #如果没有出现异常,执行下面语句 x=next(g) print("g:",x) except StopIteration as e: #如果出现异常StopIteration,把它赋给e,执行下面的语句 print(e.value) break
前面讲到,生成器只能一个一个地取出数据,在fib函数执行过程中会中断,为什么要这样呢?有什么用吗?
它厉害在:可以在单线程的情况下实现并发效果,举个例子:
import time def custumer(name): print("{0}准备来吃包子了".format(name)) while 1: baozi = yield #每次运行到这一行时都会中断 print("包子{0}来了,被{1}吃掉了".format(baozi,name)) def producer(name): c1=custumer("老大") c2=custumer("老二") c1.__next__() c2.__next__() # next 只是在调用yield print("{0}开始做包子啦!".format(name)) for i in range(1,15,2): time.sleep(1) print("做了两个包子") c1.send(i) # send 调用yield的同时给它传值 c2.send(i+1) producer("alex")
如果在自己的解释器上执行,会发现一个程序有三个任务交错切换运行,看上去就像三个任务同时在进行。
三、迭代器
我们已经知道,可以直接作用于for
循环的数据类型有以下几种:
一类是集合数据类型,如list
、tuple
、dict
、set
、str
等;
一类是generator
,包括生成器和带yield
的generator function。
这些可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
可以使用isinstance()
判断一个对象是否是Iterable
对象。
for循环本质上时不断调用next()函数实现的:
a=[1,2,3,4,5] for x in a: print(x) #完全等价于 it=iter(a) # 将列表转化成迭代器对象 while 1: try: x=next(it) #获得下一个值 print(x) except StopIteration: break #遇到StopIteration异常就跳出循环
在文件操作时,
for line in f: print(line)
每次输出其实都是调用next()函数,在Python3中已经看不出是一个迭代器了。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
hi-nginx-javascript vs node.js
hi-nginx-1.4.9已经支持javascript,这意味着把javascript应用于后端开发,将不再只有nodejs这唯一的途径和方法。由于java本身对javascript的极好支持,使得在hi-nginx中,可以直接在javascript脚本中使用java——相当于把java嵌入了javascript。因此,你随时可以用java写个库或者类,然后在javascript中随便玩。这比用c/c++写node.js扩展舒服多了。 那么,hi-nginx-javascript VS node.js,如何呢?来个node.js (6.12.0)helloworld比较下。 fedora 25,4g,2core,i5,笔记本 node.js: var http = require('http'); http.createServer(function (request, response) { // 发送 HTTP 头部 // HTTP 状态值: 200 : OK // 内容类型: text/plain response.writeHead(200, {'Content-Type':...
- 下一篇
最详细的JavaWeb开发基础之java环境搭建(Mac版)
阅读文本大概需要 5 分钟。 我之前分享过在 Windows 下面配置 Java 环境,这次给大家带来的是 Mac 下面安装配置 Java 环境。首先 Mac 系统已经带有默认的 Java,但是由于使用不方便,这里教大家一个比较方便的方法,并且管理方便。也方便我们后面配置 IDEA, Eclipse。 下面开始我们 Java 环境的安装配置。 1、打开 Java 官网 http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 在这里你可以选择你要下载的 java 版本,这里我们以 java1.8 为例来讲解。在下载之前,我们需要先接受协议才能够去下载。 这里说明下,我为什么去选择 linux 版本的 JDK 去下载,而不是 Mac 版本的 JDK,这里主要是因为如果我们下载了 Mac 版本的 JDK 在安装的时候,我们是无法去选择安装目录的,也就是它默认安装之后,你还是要在接着去 Google Mac 下 JDK 默认的安装目录,这样岂不是多次一举。其次,因为 Mac 就...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS8安装Docker,最新的服务器搭配容器使用
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS关闭SELinux安全模块
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8编译安装MySQL8.0.19