Python中的迭代器与生成器

迭代器和生成器首先都是容器,我们先来看一下容器的概念:
容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in, not in关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特例,并不是所有的元素都放在内存,比如迭代器和生成器对象)在Python中,常见的容器对象有:

list, deque, ….
set, frozensets, ….
dict, defaultdict, OrderedDict, Counter, ….
tuple, namedtuple, …
str
容器比较容易理解,因为你就可以把它看作是一个盒子、一栋房子、一个柜子,里面可以塞任何东西。从技术角度来说,当它可以用来询问某个元素是否包含在其中时,那么这个对象就可以认为是一个容器,比如 list,set,tuples都是容器对象。
尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力,当然并不是所有的容器都是可迭代的,比如:Bloom filter,虽然Bloom filter可以用来检测某个元素是否包含在容器中,但是并不能从容器中获取其中的每一个值,因为Bloom filter压根就没把元素存储在容器中,而是通过一个散列函数映射成一个值保存在数组中。
以上内容摘自http://python.jobbole.com/87805/
生成器
现在先来看一下一个简单的列表生成式:

a=[i*2 for i in range(10)]
print (a)

结构为:

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

a就是一个通过列表生成式生成的list,现在碰到一个这样的问题,现在上式中的range(10)变为了range(100000),那这组数据在内存中怎么存放比较合适呢?总不可能a=[i*2 for i in range(100000)],这样对待内存是不是很不友善。这时候generator就登场了。

b=(i*2 for i in range(10))
for i in  b:
    print (i)

结果为:

0
2
4
6
8
10
12
14
16
18

再来看看a,b分别所占的内存

import sys
a=[i*2 for i in range(10)]
print sys.getsizeof(a)
print type(a)
b=(i*2 for i in range(10))
print b
print sys.getsizeof(b)
print type(b)

运行结果如下:

100
<type 'list'>
<generator object <genexpr> at 0x02C10CB0>
40
<type 'generator'>

我们发现存储一组同样大小的数据,为什么b的内存占用会远小于a呢?看了a,b的数据类型之后我们发现a是list型,其每个元素都对有实际的物理地址与其对应,而b是generator型,其存储的是一个公式,表式了各个元素和其当前位置的关系,只有在调用时才会生成相应的数据。显然对于有规律的数据,generator明显要比list在内存中占用空间更少。

import sys
a=[i*2 for i in range(100000)]
print sys.getsizeof(a)
print type(a)
b=(i*2 for i in range(100000))
print b
print sys.getsizeof(b)
print type(b)
412236 
<type 'list'>
<generator object <genexpr> at 0x02B00CB0>
40  #b占用大小还是40
<type 'generator'>

将range(10)改为range(100000),generator的优势就很明显了。
下面是先了解一下python中生成器的三个特点:
1.只有在调用时才会生成相应的数据
2.只记录当前位置
3.只有一个.next()方法可以取到下一个值
第一条,第二条已经通过上面的例子相信已经能很清楚了,生成器其实不存储数据的,只记录当前位置和数据之间的关系,并当且仅当被调用时才生成相应的数据。

b=(i*2 for i in range(10))
print b.next()
print b.next()
print b.next()
print b.next()
print b.next()
print b.next()
print b.next()
print b.next()
print b.next()
print b.next()

结果如下

0
2
4
6
8
10
12
14
16
18

.next()方法取值,是选取下一个值。如果取值超出范围则会return StopIteration 异常。
yield关键字
简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator。

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

g = fib(10)
while True:
    try:
        x = next(g)
        print('g:', x)
    except StopIteration as e:
        print('Generator return value:', e)
        break

以上是通过生成器生成的斐波那契数列的代码,yield b 相当于return b 并在此处打了个断点保存了当且状态,下次next时从yield处直接载入。
通过Yield 可以实现单线程情况下实现并发运行的效果


import time
def comsumer(name):
    print("%s准备开始吃饭啦"%(name))
    while True:
        obj=yield
        print("%s被%s吃了"%(obj,name))

def productor(name):
    coms_1=comsumer("老王")
    coms_2=comsumer("老宋")
    coms_1.__next__()
    coms_2.__next__()
    print("%s准备做饭了"%(name))
    for i in range(10):
        time.sleep(1)
        coms_1.send("饭"+str(i)) #通过send()方法给yield赋值
        coms_2.send("饭"+str(i))

productor("小马")

以上是最简单的生产者与消费者的问题,由“小马”每隔1秒生产饭x,分别给“老王”和“老宋吃”。当程序运行很快时可以看做是并发的效果。

迭代器
看完上面的生成器(Iterator),下面迭代器就非常好理解了。
1。哪些类型是iterable
凡是可作用于for循环的对象都是Iterable类型
2。哪些类型是iterator
凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型
3。iterable和iterator的区别
这两个概念之间有一个包含与被包含的关系,如果一个对象是迭代器,那么这个对象肯定是可迭代的;但是反过来,如果一个对象是可迭代的,那么这个对象不一定是迭代器。生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
4。如何判断一个对象是否为iterable or iterator
isinstance()方法可判断一个对象是否是Iterator对象:

from collections import Iterator,Iterable
print(isinstance([1,2],Iterable))
print(isinstance([1,2],Iterator))

结果为

True
False

我们可以发现list类型,是iterable但不是iterator

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值