python生成器与迭代器详解


Posted in Python onJanuary 01, 2019

列表生成式:

例一:

a = [i+1 for i in range(10)]
print(a)

输出:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

例二:

L = [1, 2, 3, 4, 5]
print([i*i for i in L if i>3])

输出:

[16, 25]

例三:

L = [1, 2, 3, 4, 5]
I = [6, 7, 8, 9, 10]
print([i*a for i in L for a in I if i > 2 if a < 8])

输出:

[18, 21, 24, 28, 30, 35]

生成器:

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

示例:

L = [1, 2, 3, 4, 5]
I = [6, 7, 8, 9, 10]
g = (i*a for i in L for a in I )
print(g)

输出:

<generator object <genexpr> at 0x00000276586C1F48>

创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。

我们可以直接打印出list的每一个元素,可以通过generator的next()方法

next(g)

例一:

L = [1, 2, 3, 4, 5]
I = [6, 7, 8, 9, 10]
g = (i*a for i in L for a in I )
print(next(g))
print(next(g))
print(next(g))

输出:

6
7
8

例二:

L = [1, 2, 3, 4, 5]
I = [6, 7, 8, 9, 10]
g = (i*a for i in L for a in I if i > 2 if a < 8)
print(next(g))
print(next(g))
print(next(g))

输出:

18
21
24

因为generator保存的是算法,每次调用next(g)就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。正确的方法是使用for循环,因为generator也是可迭代对象:

例三:

g = (i*i for i in range(0, 5))
for i in g:
    print(i)

当我们创建了一个generator后,基本上永远不会调用next()方法,而是通过for循环来迭代它。

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print b
        a, b = b, a + b
        n = n + 1

上面的函数可以输出斐波那契数列的前N个数:

>>> fib(6)
1
1
2
3
5
8

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

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'

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

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

输出:

<generator object fib at 0x0000023DC66C1F48>

调用方法:   ##但是用for循环调用generator时,\
            ##发现拿不到generator的return语句\
            ##的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

for i in fib(5):
    print(i)

输出:

1
1
2
3
5

或者:

date = fib(5)
print(date.__next__())
print(date.__next__())
print(date.__next__())
print('test')
print(date.__next__())
print(date.__next__())

输出:

1
1
2
test
3
5

send方法有一个参数,该参数指定的是上一次被挂起的yield语句的返回值

还可通过yield实现在单线程的情况下实现并发运算的效果

#_*_coding:utf-8_*_
__author__ = 'Alex Li'

import time
def consumer(name):
  print("%s 准备吃包子啦!" %name)
  while True:
    baozi = yield

    print("包子[%s]来了,被[%s]吃了!" %(baozi,name))


def producer(name):
  c = consumer('A')
  c2 = consumer('B')
  c.__next__()
  c2.__next__()
  print("老子开始准备做包子啦!")
  for i in range(10):
    time.sleep(1)
    print("做了2个包子!")
    c.send(i)
    c2.send(i)

producer("alex")

通过生成器实现协程并行运算

迭代器:

可以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如list、tuple、dict、set、str等;

一类是generator,包括生成器和带yield的generator function。

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。

可以使用isinstance()判断一个对象是否是Iterable对象:

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

*可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

可以使用isinstance()判断一个对象是否是Iterator对象:

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

把list、dict、str等Iterable变成Iterator可以使用iter()函数:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

为什么list、dict、str等数据类型不是Iterator?

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

小结:

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python3的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

实际上完全等价于:

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
    try:
        # 获得下一个值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break

Python 相关文章推荐
python连接MySQL、MongoDB、Redis、memcache等数据库的方法
Nov 15 Python
python中enumerate的用法实例解析
Aug 18 Python
分享python数据统计的一些小技巧
Jul 21 Python
python万年历实现代码 含运行结果
May 20 Python
python实现简单的文字识别
Nov 27 Python
Python中一般处理中文的几种方法
Mar 06 Python
详解Python3中的 input() 函数
Mar 18 Python
keras 自定义loss损失函数,sample在loss上的加权和metric详解
May 23 Python
Python库安装速度过慢解决方案
Jul 14 Python
Pycharm配置lua编译环境过程图解
Nov 28 Python
Pytorch反向传播中的细节-计算梯度时的默认累加操作
Jun 05 Python
python中使用 unittest.TestCase单元测试的用例详解
Aug 30 Python
使用python3实现操作串口详解
Jan 01 #Python
python实现生成字符串大小写字母和数字的各种组合
Jan 01 #Python
python 内置模块详解
Jan 01 #Python
python配置grpc环境
Jan 01 #Python
python制作mysql数据迁移脚本
Jan 01 #Python
在python中将字符串转为json对象并取值的方法
Dec 31 #Python
对python中Json与object转化的方法详解
Dec 31 #Python
You might like
php-perl哈希算法实现(times33哈希算法)
2013/12/30 PHP
php 启动报错如何解决
2014/01/17 PHP
PHP添加图片水印、压缩、剪切的封装类
2015/08/17 PHP
php对接java现实加签验签的实例
2016/11/25 PHP
php利用云片网实现短信验证码功能的示例代码
2017/11/18 PHP
浅谈laravel orm 中的一对多关系 hasMany
2019/10/21 PHP
javascript 自动填写表单的实现方法
2010/04/09 Javascript
JQuery对checkbox操作 (循环获取)
2011/05/20 Javascript
JavaScript中为什么null==0为false而null大于=0为true(个人研究)
2013/09/16 Javascript
JavaScript中自定义事件用法分析
2014/12/23 Javascript
JS获取下拉框显示值和判断单选按钮的方法
2015/07/09 Javascript
关于网页中的无缝滚动的js代码
2016/06/09 Javascript
Javascript点击按钮随机改变数字与其颜色
2016/09/01 Javascript
jquery表单提交带错误信息提示效果
2017/03/09 Javascript
AngularJS自定义指令实现面包屑功能完整实例
2017/05/17 Javascript
JS实现字符串翻转的方法分析
2018/08/31 Javascript
CryptoJS中AES实现前后端通用加解密技术
2018/12/18 Javascript
解析原来浏览器原生支持JS Base64编码解码
2019/08/12 Javascript
vue从一个页面跳转到另一个页面并携带参数的解决方法
2019/08/12 Javascript
微信小程序动态评分展示/五角星展示/半颗星展示/自定义长度展示功能的实现
2020/07/22 Javascript
jquery实现加载更多&quot;转圈圈&quot;效果(示例代码)
2020/11/09 jQuery
vue 使用微信jssdk,调用微信相册上传图片功能
2020/11/13 Javascript
python使用xauth方式登录饭否网然后发消息
2014/04/11 Python
python 执行shell命令并将结果保存的实例
2018/05/11 Python
使用python判断jpeg图片的完整性实例
2019/06/10 Python
深入了解Django View(视图系统)
2019/07/23 Python
基于TensorBoard中graph模块图结构分析
2020/02/15 Python
python不相等的两个字符串的 if 条件判断为True详解
2020/03/12 Python
python如何调用百度识图api
2020/09/29 Python
亚马逊中国官方网站:amazon.cn
2017/05/25 全球购物
物流管理毕业生自荐信
2013/10/24 职场文书
实习鉴定评语
2014/01/19 职场文书
机械设计及其自动化求职推荐信
2014/02/17 职场文书
《春到梅花山》教学反思
2014/04/16 职场文书
先进教育工作者事迹材料
2014/12/23 职场文书
手残删除python之后的补救方法
2021/06/26 Python