python exercise function之yield生成器

yield关键字用来定义生成器,必须在函数内部使用。当函数内部使用yield生成器时,该函数就是一个生成器函数。与普通函数的区别是当使用函数名调用函数时,函数并不执行。必须使用函数实例调用__next__()方法或者send()方法,每次返回一个yield对应的值。首次函数从函数头开始执行到第一个yield处,返回yield对应值,然后停止,并保存函数执行位置信息。再次调用__next__()方法或者send()方法时,从上一个yield之后开始执行,遇到下一个yield再次停止,依次循环,直到迭代元素全部迭代完成。这时如果再调用__next__()方法或者send()方法时,会产生StopIteration异常。

这样一个显而易见的好处就是迭代过程中使用的内存始终是一个常量。迭代过程中不会像普通列表那样一次存储全部迭代元素耗费大量内存,而是在迭代过程中需要的元素不断通过计算获得。就好像python2中range和xrange之间的区别,range返回的是一个列表,内存中需要存储全部元素后,再根据需要一个一个迭代出来,而xrange返回一个可迭代对象,用其惰性迭代的特性,内存中需要保存的只是单个值,for循环中每访问一次就从xrange对象中计算并取一个值返回即可。


示例1 普通函数与yield生成器函数

def test_no_yield_print():
    for i in range(3):
        print(i, end=' ')


print(test_no_yield_print(), '函数体内使用print打印迭代值,虽然不需要耗费内存储存整个迭代列表,但其返回值是None,函数复用性较差')
print('#'*30)


def test_no_yield_return():
    alist = []
    for i in range(3):
        alist.append(i)
    return alist


print(test_no_yield_return(), '使用return返回列表,函数复用性较好,但需要开辟内存空间来存储list,如果列表较大会耗费较多内存')
print('#'*30)


def test_yield():
    print('this is not yield block')
    for i in range(3):
        print('yield before')
        yield i
        print('yield after')
    print('this is out of for block')


a = test_yield()
print(a.__next__(), '\n', '首次调用从函数头开始执行,执行完第一个yield停止')
print('#'*30)
print(a.__next__(), '\n', '下次调用从yield之后开始执行,遇到下一个yield停止')
print('#'*30)
print(a.__next__(), '\n', '重复上次调用的逻辑')
print('#'*30)
print(a.__next__(), '已经没有可迭代元素,返回StopIteration异常')


#输出

0 1 2 None 函数体内使用print打印迭代值,虽然不需要耗费内存储存整个迭代列表,但其返回值是None,函数复用性较差
##############################
[0, 1, 2] 使用return返回列表,函数复用性较好,但需要开辟内存空间来存储list,如果列表较大会耗费较多内存
##############################
this is not yield block
yield before
0 
 首次调用从函数头开始执行,执行完第一个yield停止
##############################
yield after
yield before
1 
 下次调用从yield之后开始执行,遇到下一个yield停止
##############################
yield after
yield before
2 
 重复上次调用的逻辑
##############################
Traceback (most recent call last):
  File "F:/PythonProject/my_project/geektime/testxxxc.py", line 36, in <module>
    print(a.__next__(), '已经没有可迭代元素,返回StopIteration异常')
StopIteration

test_yield函数需要实例化之后,通过实例调用,而不是直接调用函数本身。for循环外的print('this is not yield block')只在首次调用__next__()方法时,函数顺序执行而执行。而print('this is out if for block')也是在最后一次调用print(a.__next__(), '已经没有可迭代元素,返回StopIteration异常')执行一次。期间所有遇到yield的中断和继续执行都是在for循环内进行的。

def test_yield():
    print('this is not yield block')
    for i in range(3):
        print('yield before')
        yield i
        print('yield after')
    print('this is out of for block')


b = test_yield()
for j in b:
    print(j)


#输出
this is not yield block
yield before
0
yield after
yield before
1
yield after
yield before
2
yield after
this is out of for block

通常情况下会使用for循环进行元素的迭代,for循环自行处理了StopIteration异常,并不需要人工再进行干预。


示例2 yield生成器函数中的return

def test_yield():
    for i in range(3):
        yield i
        if i > 0:
            return

b = test_yield()
for j in b:
    print(j)


#输出
0
1

return在yield生成器函数中,不再起到返回值的作用,因为每次调用都由yield返回值,而return只是标识函数结束,只能单独出现不能接任何参数,在某些条件下标识提前结束迭代。上例中i=1执行完成后,再次调用时判断条件成立,执行return直接结束了迭代。


示例3 函数生成器与列表生成器

def test_yield():
    print('first yield exec')
    yield 'first'

print(type(test_yield()))

aiter = (i*i for i in range(3))
print(type(aiter))


#输出
<class 'generator'>
<class 'generator'>

 可以看到使用yield的函数、或者在列表推导式中将[]变成()都可以产生生成器

示例4 使用yield实现一个步进为小数的range函数

def frange(start, stop, step):
    i = start
    while i < stop:
        yield i+step
        i += step


for j in frange(1, 10, 0.5):
    print(j)

示例5 使用yield实现斐波那契数列的输出

def fibonacci(n):
    a, b, c = 0, 1, 0
    while c < n:
        yield b
        a, b = b, a + b
        c += 1


f = fibonacci(10)
for i in f:
    print(i)

示例6 使用send方法对yield进行传值

def fibonacci(n):
    a, b, c = 0, 1, 0
    while c < n:
        res = yield b
        print(res)
        a, b = b, a + b
        c += 1


x = fibonacci(5)
print(next(x))
print(next(x))
print(x.send(100))
print(next(x))



#输出
1
None
1
100
2
None
3

send方法与__next__或next()方法的不同之处在于,send方法可以给yield传值。但首次调用时不能直接使用send进行传值,这样会产生TypeError异常。应该先使用__next__或next()进行一次函数调用执行到yield后,按需进行send传值。如果使用next方法进行调用时发现,用res接收yield返回值时都是None。在进行了send传值后,res接收了send传入的值。当然send方法也与next方法的其他作用相同,都会解阻一次yield循环到下一个yield处停止。


示例7 单线程使用yield模拟多线程

def simulation_multi_thread():
    while True:
        do_something = yield
        print(do_something)


police = simulation_multi_thread()
terrorist = simulation_multi_thread()

police.__next__()
terrorist.__next__()

police.send('Game Start')
terrorist.send('Game Start')

police.send('police Gun is ready')
terrorist.send('terrorist Gun is ready')

police.send('police knife is ready')
terrorist.send('terrorist knife is ready')

police.send('gogogo')
terrorist.send('gogogo')

police.close()
terrorist.close()


#输出
Game Start
Game Start
police Gun is ready
terrorist Gun is ready
police knife is ready
terrorist knife is ready
gogogo
gogogo

使用yield中断/恢复的特性,模拟了反恐精英里面警匪的两个线程,可见两个实例初始化后,之间相互并不干扰,使用send方法每次传递不同的值给yield,最后通过close方法关闭迭代


示例8 yield实现生产者、消费者异步操作

def gen_data_from_file(file_name):
    for line in open(file_name):
        yield line


def gen_words(line):
    for word in (w for w in line.split() if w.strip()):
        yield word


def count_words(file_name):
    word_map = {}
    for line in gen_data_from_file(file_name):
        for word in gen_words(line):
            if word not in word_map:
                word_map[word] = 0
            word_map[word] += 1
    return word_map


def count_total_chars(file_name):
    total = 0
    for line in gen_data_from_file(file_name):
        total += len(line)
    return total


if __name__ == '__main__':
    print(count_words(r'F:\pythonfiletest\test.txt'), count_total_chars(r'F:\pythonfiletest\test.txt'))

 gen_data_from_file和gen_words两个函数是生产者,而count_words和count_total_chars两个函数是消费者,用于统计文件中单词重复出现的次数,以及总的字母数量。而 gen_data_from_file和gen_words并不是一次将文件内容全部读取并返回,而是使用yield,在每次消费者函数需要数据时在进行数据获取。 


示例9 yield from

def test_yield():
    for i in range(4):
        yield i


def test_yield_from():
    yield from range(4)

使用yield from用于简化for循环的写法


 总结:

1、yield关键字用来定义生成器函数。

2、对于生成器函数取值可以通过next、__next__、send等方法进行单值的获取,会产生StopIteration异常。更通常的做法是使用for循环取值,自动处理StopIteration异常

3、产生生成器可以使用yield函数或者列表生成器,对于普通的计算类简单生成器使用列表生成器即可。对于较复杂的需求,还是要使用yield函数

4、使用next、__next__进行yield取值,send方法还可以对yield进行传值

5、return在yield函数中起到标识函数结束迭代的作用,只有在产生StopIteration异常时才能获得return的参数

6、yield生成器函数的多个实例之间互不干扰

7、生成器函数单独调用时,并不执行。而在实际调用next、__next__、send等方法时,才开始执行。

8、使用close方法关闭迭代,使用throw方法在生成器调用端抛出异常

9、使用yield from结构可以简化for循环的写法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值