python迭代器与生成器

python迭代器与生成器

差不多弄懂了迭代器与生成器,都是东拼西凑的,也加入了自己的理解。

1. 迭代器(Iterator)

1.1 简单操作

迭代器表示的是一个数据流,迭代器可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
迭代器的基本操作:iter()用于生成迭代器,next()访问下一元素。

list = [1, 2, 3, 4]
it = iter(list)  #list是列表不是迭代器,需要用iter()生成迭代器
next(it)
next(it)

结果是

1
2

1.2 用for遍历迭代对象

for可以直接用列表生成迭代器

for x in list:
    print(x)

输出

1
2
3
4

for同样可以对迭代器it直接遍历:

for x in it:
    print(x)

输出:

3
4

(前面已经调用了两次next(),这里接着访问下一元素)
所有元素访问结束后会触发StopIteration异常。

注:此后无法再次从头开始访问元素:

for x in it:    #接着访问已经结尾的迭代对象
    print(x)

什么都没输出。想要再重新访问(估计没人像我这么无聊访问一遍又一遍)那就再it=iter(list)生成一遍咯。

1.3 用next()遍历迭代对象

while循环:

list = [1, 2, 3, 4]
it = iter(list)

while True:
    try:
        print(next(it), end = '')  #放在同一行输出
    except StopIteration:
        pass    #也可以import sys后调用sys.exit()

输出:

1 2 3 4 

2. 生成器(generator)

说实话python这么多什么什么器让我有点不知所措……好在生成器没那么难理解。
生成器特点:生成器只能用于迭代操作。
创建方式:包含yield语句的普通函数、圆括号()下的生成器表达式

2.1 圆括号()下的生成器表达式

生成器表达式语法和列表推导式相同,列表推导式是以大括号的形式存在。列表推导式是直接创建一个列表,但是由于受到内存的限制,列表的容量有上限,而生成器则不需要一下子创建完整的列表,而是一边循环一边计算。(引用于[2])

ge = (2*i for i in range(3))
print(next(ge))   #输出 0
print(ge.__next__())  #输出 2
print(next(ge))   #输出 4
print(next(ge))   #StopIteration

直到生成器中没有其他元素时,抛出StopIteration异常
for循环可以迭代获取生成器对象中的每个元素,并且不会产生StopIteration异常:

ge = (2*i for i in range(3))  #上次已经迭代结束了,又要再生成一遍,否则什么也不输出哦
for g in ge:
    print(g)

结果

0
2
4

2.2 含有yield语句的普通函数

调用一个生成器函数,返回的是一个迭代器对象。迭代器控制生成器函数的执行。
当函数开始运行,执行到第一个yield语句时暂停,将yield表达式后的表达式的值返回给调用者。

def wine():
    print('first yield...')
    yield 1   
    print('second yield...')
    yield 2

ww = wine()  # ww是一个迭代器,由生成器返回生成

while True:
    try:
        print(next(ww))  #使用next()启动函数
    except StopIteration:
        pass

输出

first yield...
1
second yield...
2
send()方法与next()函数

在函数重新运行时,其实上次暂停处的yield表达式会先接收一个值作为结果,然后才接着运行直到碰到下一个yield表达式。

def wine():
    print('first yield...')
    x = yield 1   #要有返回值给x
    print(x)    #这里x接受send()方法返回的值
    print('second yield...')
    yield 2

ww = wine()  # ww是一个迭代器,由生成器返回生成

#下面要使用send()方法启动函数
print(ww.send(None)) #也可以用print(next(ww))或者print(ww.__next__())
print(ww.send('the result of first yield...'))

注:在实例化生成器后不能直接调用send()方法启动函数,因为此时没有yield表达式可以接收其返回的值,应该使用send(None)方法,或者在实例化生成器后首先使用next()函数。
输出:

first yield...
1   #仅输出yield返回的值
the result of first yield...   #调用第二次send后输出yield表达式产生的结果x
second yield...
2
close()方法

当迭代器数据流还没有执行完但要终止生成器,可以调用close()方法。为了有效监控迭代器的运行,可以将yiled表达式用try..except..finally结构中,在调用close()前可以执行finally中的代码

def wine():
    try:
        print('first yield...')
        x = yield 1   #要有返回值给x
        print(x)   #这里x接受send()方法返回的值
        print('second yield...')
        yield 2
    except Exception as e:
        pass
    finally:
        print('yield is closed.')

ww = wine()  # ww是一个迭代器,由生成器返回生成

print(next(ww))
print(ww.close())

输出:

first yield...
1
yield is closed.
None

3. yield 生成器应用场景

3.1 状态机

可以看到generator帮助进行了状态维护,以此让连续代码片段分段执行的能力。
“连续代码分段执行”同样也是有限状态机的思想,状态机的四要素是现态、条件、动作、次态,用条件触发动作将状态从现态迁移到次态,“分段执行”的意思是在一个状态中程序可以去完成其他任务,接受条件触发后继续执行原本“连续代码”。

def someGenerator():
    ……  #执行动作
    yield ……  #得到新状态,返回
    ……   #继续处理
ge = someGenerator()

置位起始状态
while True:
    try:
        ……其他任务代码……
    except 条件满足或其他异常:
        next(ge)或ge.send() #去执行动作
        得到新状态,累加    #累加只是举个例子
        if 累加后新状态满足继续处理的要求:
            ge.send()
    break

在实际中如何应用generator的特性?当然有不少开源项目充分利用了generator,但在具体实际开发中有哪些部分可以直接利用generator而无需引入外界框架呢?

目前遇到的可能合适场景有,

资源的分块缓存加载 在加载完部分资源之后调用yield,等待外界调用next再次加载。通过generator维护加载完成状态。

异步代码同步化实现 (引用于[3])

# 异步callback(回调)执行方式

def foo():
  ...
  do_something_a(lambda data: complete_do_something_a(data))

def complete_do_something_a(data):
  ...
  do_something_b(lambda data: complete_do_something_b(data))

def complete_do_something_b(data):
  ...

# generator方式执行

def foo():
  data = yield do_something_a() #此后等待再次调用yield
  yield do_something_b(data)

3.2 分步生成完整的数据-节省内存

一个函数 f,f 返回一个 list,这个list是动态计算出来的(不管是数学上的计算还是逻辑上的读取格式化),并且这个list会很大(无论是固定很大还是随着输入参数的增大而增大),这个时候,我们希望每次调用这个函数并使用迭代器进行循环的时候一个一个的得到每个list 元素而不是直接得到一个完整的list来节省内存,这个时候 yield 就很有用。(引用于[4])

如返回一个n个数的斐波那契数列,一般是这么做的:

def fab(max): 
   n, a, b = 0, 0, 1 
   L = [] 
   while n < max: 
       L.append(b) 
       a, b = b, a + b 
       n = n + 1 
   return L

f = fab(1000)
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        sys.exit()

yield方式:

def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield a      # 使用 yield
        a, b = b, a + b 
        n = n + 1

for n in fab(5):  #也可以是next
    print n    #逐次处理每一个元素,

执行到 yield a时,fab 函数就返回一个迭代值,下次迭代时,代码从yield a的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到yield,这样内存消耗始终为常数。

另外一种写法(记录另一个问题):

import sys

def fibonacci(n): # 生成迭代器 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n):
            return
        print(' a={} b={} counter={}'.format(a, b,counter))#这里格式化输出没有写好,再改
        yield a
        a, b = b, a+b
        counter += 1

f = fibonacci(10) # 10的作用是if语句的return
while True:
    try:
        print('fib={}'.format(next(f)), end = '')
    except StopIteration:
        sys.exit()

输出:

       a=0 b=1  counter=0
fib=0  a=1 b=1  counter=1
fib=1  a=1 b=2  counter=2
fib=1  a=2 b=3  counter=3
fib=2  a=3 b=5  counter=4
fib=3  a=5 b=8  counter=5
fib=5  a=8 b=13  counter=6
fib=8  a=13 b=21 counter=7
fib=13 a=21 b=34 counter=8
fib=21 a=34 b=55 counter=9
fib=34 a=55 b=89 counter=10
fib=55

引发异常,程序终止是因为后面判断conuter=11>n为True,返回。

参考文献:
[1]菜鸟教程:http://www.runoob.com/python3/python3-iterator-generator.html
[2]wenkefendou > Python3 学习文档https://wenkefendou.gitbooks.io/python3-learning/content/generator_and_yield.html
[3]SOLILOQUIZE Python Generator使用场景思考 http://blog.soliloquize.org/2016/09/02/Python-Generator%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E6%80%9D%E8%80%83/
[4]Python yield 使用浅析http://www.runoob.com/w3cnote/python-yield-used-analysis.html

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值