Python生成器定义与简单用法实例分析


Posted in Python onApril 30, 2018

本文实例讲述了Python生成器定义与简单用法。分享给大家供大家参考,具体如下:

一、什么是生成器

在Python中,由于受到内存的限制,列表容量肯定是有限的。例如我们创建一个包含一亿个元素的列表,Python首先会在内存中开辟足够的空间来存储这个包含一亿个元素的列表,然后才允许用户去使用这个列表,这就可能会导致以下问题:

1、内存中没有足够的内存空间开存储这个列表,从而导致列表无法创建

2、即使列表成功创建,然而仍会消耗很长的时间,导致程序效率低下

3、若用户只想访问列表前面的几个元素,则后面列表绝大多数元素占用的空间就都白白浪费了

为了有效解决以上的问题,Python中引入了一种“一边循环,一边计算”的新机制,即当用户需要使用某个对象时,Python才根据事先设计好的规则开辟内存空间创建这个对象供用户使用,而不是像列表一样事先将所有的对象都创建完毕之后再提供给用户使用。这种机制在Python中成为生成器(generator)。

二、生成器的创建

A、生成器推到式

与列表推到式类似,只不过生成器推导式使用()而非[],并且最终返回的是生成器而非列表

g=((i+2)**2 for i in range(2,30)) #g是一个生成器
print(g) #g为空,里面包含任何元素

运行结果:

<generator object <genexpr> at 0x0000000002263150>

B、yield关键字

在一个函数定义中包含yield关键字,则这个函数就不再是一个普通的函数,而是一个生成器(generator)

[说明]:yield指令可以暂停一个函数并返回其中间结果,使用该指令的函数将保存执行环境,并在必要时恢复

def fib(max):
  n,a,b=0,0,1
  while n<max:
    #print(b)
    yield b
    a,b=b,a+b
    n+=1
  return 'done'
f=fib(6)
print(f)

运行结果:

<generator object fib at 0x0000000002553150>

[注]:普通函数和变成生成器的函数的不同:

普通函数是顺序执行的,遇到return或是最后一行函数语句就返回。而变成生成器的函数在每次调用__next__()方法时执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行

f=fib(6)
print(f)
print(f.__next__())
print(f.__next__())
print('暂停一下')
print(f.__next__())
print(f.__next__())

运行结果:

<generator object fib at 0x00000000025631A8>
1
1
暂停一下
2
3

三、生成器方法(参考:伯乐在线)

1.close()方法:手动关闭生成器函数,后面的调用会直接返回StopIteration异常

def func():
  yield 1
  yield 2
  yield 3
g=func()
g.__next__()
g.close() #手动关闭生成器
g.__next__() #关闭后,yield 2和yield 3语句将不再起作用

运行结果:

Traceback (most recent call last):
  File "E:\py3Demo\Hello\generatorDemo.py", line 9, in <module>
    g.__next__() #关闭后,yield 2和yield 3语句将不再起作用
StopIteration

2.__next__()方法:返回生成器的下一次调用

def func():
  n=1
  for i in range(3):
    yield n
    n+=1
c=func()
a1=c.__next__()
a2=c.__next__()
a3=c.__next__()

[流程解释]:

对于普通的生成器,第一个__next__()方法的调用相当于启动生成器,此时会从生成器函数的第一行开始执行,直到第一次执行完yield语句(第四行)后,跳出生成器函数。

当调用第二个__next__()方法后,会重新进入生成器函数,并从yield语句的下一条语句(第五行)开始执行,直到重新运行到yield语句,执行后再次跳出生成器函数。

后面的__next__()方法调用以此类推

3.send()方法:接受外部传入的一个变量,并根据变量内容计算结果返回到生成器函数中

[注]:

(1)send()方法和__next__()方法相似,区别在于send()方法可以传递给yield表达式值,而__next__()方法不能传递特定的值,只能传递None给yield表达式,因此可以将generator.__next__()理解为generator.send(None)

(2)第一次调用生成器函数时,必须使用__next__()语句或是send(None),不能使用send发送一个非None的值给生成器函数,否则会出错,因为没有yield语句来接收这个值

def gen():
  value=0
  while True:
    receive=yield value
    if receive=='end':
      break
    value='Got:%s' %receive
g=gen()
print(g.__next__()) #或是print(g.send(None)),从而启动生成器
print(g.send('aaa'))
print(g.send(3))
print(g.send('end'))

运行结果:

0
Got:aaa
Got:3
Traceback (most recent call last):
  File "E:\py3Demo\Hello\generatorDemo.py", line 13, in <module>
    print(g.send('end'))
StopIteration

[流程解释]:

a.通过g.send(None)或g.__next__()启动生成器函数,并执行到第一个yield语句结束的位置并将函数挂起。此时执行完了yield语句,但是没有给receive赋值,因此yield value会输出value的初始值0

b.g.send('aaa')先将字符串‘aaa'传入到生成器函数中并赋值给receive,然后从yield语句的下一句重新开始执行函数(第五句),计算出value的值后返回到while头部开始新一轮的循环,执行到yield value语句时停止,此时yield value会输出‘Got:aaa',然后挂起

c.g.send(3)重复步骤b,最后输出结果为‘Got:3'

d.g.send('end')会使程序执行break然后跳出循环,从而函数执行完毕,得到StopIteration异常

4.throw()方法:向生成器发送一个异常。

def gen():
  while True:
    try:
      yield 'normal value' #返回中间结果,此处的yield和return的功能相似
      yield 'normal value2'
      print('I am here')
    except ValueError:
      print('We got ValueError')
    except Exception:
      print('Other errors')
      break
g=gen()
print(g.__next__())
print(g.throw(ValueError))
print(g.__next__())
print(g.throw(TypeError))

运行结果:

Traceback (most recent call last):
  File "E:\py3Demo\Hello\generatorDemo.py", line 17, in <module>
    print(g.throw(TypeError))
StopIteration
normal value
We got ValueError
normal value
normal value2
Other errors

[解释]:

a.print(g.__next__())会输出normal value,并停在yield 'normal value2'之前

b.由于执行了g.throw(ValueError),所以回跳过后续的try语句,即yield ‘normal value2'不会执行,然后进入到except语句,打印出‘We got ValueError'。之后再次进入到while语句部分,消耗一个yield,输出normal value

c.print(g.__next__())会执行yield ‘normal value2'语句,并停留在执行完该语句后的位置

d.g.throw(TypeError)会跳出try语句,因此print('I am here')不会被执行,然后打印‘Other errors',并执行break语句跳出while循环,然后到达程序结尾,打印StopIteration异常的信息

四、生成器的运用

import time
def consumer(name):
  print('%s准备吃包子啦!' %name)
  while True:
    baozi=yield #接收send传的值,并将值赋值给变量baozi
    print('包子[%s]来了,被[%s]吃了!' %(baozi,name))
def producer(name):
  c1=consumer('A') #把函数变成一个生成器
  c2=consumer('B')
  c1.__next__()#调用这个方法会走到yield处暂时返回
  c2.__next__()
  print('开始准备做包子啦!')
  for i in range(10):
    time.sleep(1)
    print('做了一个包子,分成两半')
    c1.send(i)
    c2.send(i)
producer('Tomwenxing')

运行结果:

A准备吃包子啦!
B准备吃包子啦!
开始准备做包子啦!
做了一个包子,分成两半
包子[0]来了,被[A]吃了!
包子[0]来了,被[B]吃了!
做了一个包子,分成两半
包子[1]来了,被[A]吃了!
包子[1]来了,被[B]吃了!
做了一个包子,分成两半
包子[2]来了,被[A]吃了!
包子[2]来了,被[B]吃了!
做了一个包子,分成两半
包子[3]来了,被[A]吃了!
包子[3]来了,被[B]吃了!
做了一个包子,分成两半
包子[4]来了,被[A]吃了!
包子[4]来了,被[B]吃了!
做了一个包子,分成两半
包子[5]来了,被[A]吃了!
包子[5]来了,被[B]吃了!
做了一个包子,分成两半
包子[6]来了,被[A]吃了!
包子[6]来了,被[B]吃了!
做了一个包子,分成两半
包子[7]来了,被[A]吃了!
包子[7]来了,被[B]吃了!
做了一个包子,分成两半
包子[8]来了,被[A]吃了!
包子[8]来了,被[B]吃了!
做了一个包子,分成两半
包子[9]来了,被[A]吃了!
包子[9]来了,被[B]吃了!

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
[原创]windows下Anaconda的安装与配置正解(Anaconda入门教程)
Apr 05 Python
Python读取视频的两种方法(imageio和cv2)
Apr 15 Python
Python装饰器知识点补充
May 28 Python
10 分钟快速入门 Python3的教程
Jan 29 Python
树莓派使用USB摄像头和motion实现监控
Jun 22 Python
Python IDE Pycharm中的快捷键列表用法
Aug 08 Python
在 Python 中接管键盘中断信号的实现方法
Feb 04 Python
python Canny边缘检测算法的实现
Apr 24 Python
在Ubuntu 20.04中安装Pycharm 2020.1的图文教程
Apr 30 Python
python中pandas.read_csv()函数的深入讲解
Mar 29 Python
python删除csv文件的行列
Apr 06 Python
Python利用Turtle绘制哆啦A梦和小猪佩奇
Apr 04 Python
Python迭代器定义与简单用法分析
Apr 30 #Python
python 实现在txt指定行追加文本的方法
Apr 29 #Python
Python 实现在文件中的每一行添加一个逗号
Apr 29 #Python
python 把文件中的每一行以数组的元素放入数组中的方法
Apr 29 #Python
详谈python3 numpy-loadtxt的编码问题
Apr 29 #Python
python3.4.3下逐行读入txt文本并去重的方法
Apr 29 #Python
Python使用re模块实现信息筛选的方法
Apr 29 #Python
You might like
PHP 存取 MySQL 数据库的一个例子
2006/10/09 PHP
PHP 在5.1.* 和5.2.*之间 PDO数据库操作中的不同之处小结
2012/03/07 PHP
强烈声明: 不要使用(include/require)_once
2013/06/06 PHP
PHP实现的封装验证码类详解
2013/06/18 PHP
PHP 字符串长度判断效率更高的方法
2014/03/02 PHP
Yii视图CGridView实现操作按钮定义地址示例
2016/07/14 PHP
PHP学习记录之数组函数
2018/06/01 PHP
使用SMB共享来绕过php远程文件包含的限制执行RFI的利用
2019/05/31 PHP
javascript验证只能输入数字和一个小数点示例
2013/10/21 Javascript
javascript去除字符串中所有标点符号和提取纯文本的正则
2014/06/07 Javascript
jQuery获取iframe的document对象的方法
2014/10/10 Javascript
Underscore.js常用方法总结
2015/02/28 Javascript
JS实现带有3D立体感的银灰色竖排折叠菜单代码
2015/10/20 Javascript
js实现页面跳转的几种方法小结
2016/05/16 Javascript
jQuery弹出层插件popShow(改进版)用法示例
2017/01/23 Javascript
基于LayUI实现前端分页功能的方法
2017/07/22 Javascript
基于zTree树形菜单的使用实例
2017/12/25 Javascript
微信小程序学习笔记之本地数据缓存功能详解
2019/03/29 Javascript
vue实现跳转接口push 转场动画示例
2019/11/01 Javascript
bootstrapValidator表单校验、更改状态、新增、移除校验字段的实例代码
2020/05/19 Javascript
Python数据分析之双色球统计两个红和蓝球哪组合比例高的方法
2018/02/03 Python
PyQt QListWidget修改列表项item的行高方法
2019/06/20 Python
TensorFlow tensor的拼接实例
2020/01/19 Python
python+adb命令实现自动刷视频脚本案例
2020/04/23 Python
Python更改pip镜像源的方法示例
2020/12/01 Python
html5 video标签屏蔽右键视频另存为的js代码
2013/11/12 HTML / CSS
企业优秀员工事迹材料
2014/05/28 职场文书
经济贸易系求职信
2014/08/04 职场文书
校长四风对照检查材料
2014/09/27 职场文书
2014年图书管理员工作总结
2014/12/01 职场文书
三八节活动主持词
2015/07/04 职场文书
2015年教导处教学工作总结
2015/07/22 职场文书
2015年教务主任工作总结
2015/07/22 职场文书
php实现自动生成验证码的实例讲解
2021/11/17 PHP
集英社今正式宣布 成立游戏公司“集英社Games”
2022/03/31 其他游戏
PostgreSQL 插入INSERT、删除DELETE、更新UPDATE、事务transaction
2022/04/12 PostgreSQL