python学习之yield

概述

为什么会出现yield,说白了就是为了减少内存消耗,减少代码冗余。为了理解这句话,我们一步一步进行

斐波那契数

斐波那契数是一个很简单的递归数列,大家都很容易写出来这一功能

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

# 结果
1
1
2
3

注意到,上述代码中加入了print,这应是不好的,函数封装性很差,不能提供给第三方所用,可复用性很差

list

为提高代码的可复用性,便以返回list为好,如下

def fib(num):
    n, a, b = 0, 0, 1
    l=list()
    while n < num:
        l.append(b)
        a, b = b, a + b
        n += 1
    return l

for i in fib(4):
    print i

注意到,fib返回list便能完成函数可复用的性能,然而list却是一次性将所有数据放入内存中,进入内存的数据越多,内存占有越大。 同理列表生成式也是一样。

因此,为了控制内存开销,尽量不要使用list。

迭代版

迭代是一个访问数值的好方法,每次迭代返回下一个数值,内存占用小始终为常数,如下

class Fib(object):
    def __init__(self, sum):
        self.sum = sum
        self.n, self.a, self.b = 0, 0, 1

    def __iter__(self):
        return self

    def next(self):
        if self.n < self.sum:
            r = self.b
            self.a, self.b = self.b, self.a + self.b
            self.n = self.n + 1
            return r
        raise StopIteration()


for i in Fib(4):
    print i

注意到,Fib类通过next()不断返回数列中的下一个值

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

for i in fib(4):
    # for循环只要遇到StopIteration就会停止
    print i

注意到,yield版很简洁,并且也符合代码可复用行的规则,比前几个版本都要简介许多,那么yield有着怎样的功能,下面继续详细介绍

说白了,yield作用就是将一个函数变成一个生成器(python解释器视为generator,是一种仅仅拥有生成某种东西的能力,如果不用next方法是获取不到值得),不再是一个普通函数, 调用 fib(4) 不会执行 fab 函数,而是返回一个 iterable 对象,如下

print fib(4)

# 结果
<generator object fib at 0x0000000001CE5168>

具体来说,在 for 循环执行时,每次循环都会执行 fib 函数内部的代码,执行到 yield b 时,fib 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。

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

temp = fib(4)
print temp.next()
print temp.next()
print temp.next()
print temp.next()
print temp.next()

# 结果
1
1
2
3
Traceback (most recent call last):
  File "D:/workspace/MyTest/y.py", line 16, in <module>
    print temp.next()
StopIteration

注意到,手动调用next()方法,便可清楚看到yield的执行流程,当函数执行完后,generator 自动抛出 StopIteration 异常,表示迭代完成,并且无需处理StopIteration 异常,循环会正常结束。

另外一种访问生成器的方法

print list(fib(4))

# 结果
[1, 1, 2, 3]

总结

  • yield类似于return,只是yield返回的是一个generator。
  • 当执行到这个函数时,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象
  • 当执行生成器的__next__ 的时候,代码会按照顺序去执行,当执行到yield时会返回并提出,yield后面的值就是返回值,然后记录代码执行的位置,并退出, 第二次执行的时候会根据上次代码执行的位置继续往下执行,如果__next__获取不到值的时候就会报StopIteration错误
  • 生成器对象通过next和send执行,类似于上一条,而next就是封装了__next__,或是使用for迭代时,函数中的代码会执行,从开始到达 yield 关键字,然后返回 yield 后的值作为第一次迭代的返回值,当继续迭代时,便会从yield后的代码执行

需要注意的是:在生成器中,如果没有return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代

迭代器

上面说了很多东西,有一个出镜率很高的词,就是迭代器,那么什么是迭代器呢,这里再补充下知识。

简单来说,迭代器具有访问生成器的能力,可以访问到生成器的值,类似于生成器的next方法,一个一个值一个值得去迭代,只能够按照顺序的去查找

  • 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
  • 不能随机访问集合中的某个值 ,只能从头到尾依次访问,就像列表一样
  • 访问到一半时不能往回退
  • 便于循环比较大的数据集合,节省内存,内存占用为常数
yield生成器类型
from inspect import isgeneratorfunction

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

print isgeneratorfunction(fib)
print isgeneratorfunction(fib(4))

import types
print isinstance(fib, types.GeneratorType)
print isinstance(fib(4), types.GeneratorType)

from collections import Iterable
print isinstance(fib, Iterable)
print isinstance(fib(4), Iterable)

# 结果
True
False
False
True
False
True

注意到,需区分 fib 和 fib(4),fib 是一个 generator function,而 fib(4) 是调用 fib 返回的一个 generator,好比类的定义和类的实例的区别,并且fib(4)是一个可迭代的对象

yield的next和send
  • next向下执行,遇到yield时返回
  • send想yield发送信息,同样遇yield时返回
def func():
    print '1'
    a = yield 'hello'
    print 'next1:'+a
    print '2'
    b = yield 'hi'
    print 'next2:'+b
    print '3'
    yield  'dad'

temp = func()
a = temp.next()
print a
b = temp.send('abs')
print b
c = temp.send('qwe')

# 结果
1
hello
next1:abs
2
hi
next2:qwe
3
def f():
    n=0
    yield n
    n+=1
    yield n
    n += 1
    yield n
    n += 1

e = f()
print e.next()
print e.next()
print e.next()

# 结果
0
1
2
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值