[PY3]——函数——生成器(yield关键字)

时间:2021-09-18 23:29:38

函数—生成器篇

 

1. 认识和区分可迭代or生成器

1.1 可迭代对象

当你建立了一个列表,你可以逐项地读取这个列表,这叫做一个可迭代对象

当你使用一个列表生成式来建立一个列表的时候,就建立了一个可迭代的对象

所有可以使用  for..in..语法的叫做一个迭代器:例如列表,字符串,文件……

经常使用它们是因为我们可以如愿的读取其中的元素,但是你把所有的值都存储到了内存中,如果你有大量数据的话这个方式并不是你想要的

mylist=[ x*x for x in range(3) ] for i in mylist:
    print(i)

for i in mylist:
    print(i)
0
1
4
0
1

4

1.2 生成器

生成器是可以迭代的,但是你只可以读取它一次,因为它并不把所有的值放在内存中,它是实时地生成数据

mygenerator=( x*x for x in range(3) ) for i in mygenerator:
    print(i)

for i in mygenerator:
    print(i)

0 1
4

 

2. 理解生成器 & yield 

2.1 生成器是惰性求值的

惰性求值也叫延迟求值,顾名思义就是表达式不会在它被绑定到变量之后就立即求值,而是等用到时再求值。

延迟求值的一个好处是能够建立可计算的无限列表而没有妨碍计算的无限循环或大小问题。

考虑到yield的特性:可在减少内存占用、避免使用递归等场景时选择yield

2.2  生成器函数的执行流程

# yield关键字的作用是把一个函数变成一个generator,称为生成器函数。
# 生成器函数的返回值是生成器
* 生成器函数执行的时候,不会执行函数体 * 当next生成器的时候, 当前代码执行到之后的第一个yield,会弹出值,并且暂停函数 * 当再次next生成器的时候,从上次暂停处开始往下执行 * 当没有多余的yield的时候,会抛出StopIteration异常,异常的value是函数的返回值

通过以下例子来理解generator执行流程:

def gen(x):
    if x!=0:
        yield x

mygenerator=gen(3)

print(mygenerator)  #当我们调用这个函数时,我们发现函数内部的代码并不立马执行,而只是返回了一个生成器对象
#<generator object gen at 0x7f63ee065888>

for i in mygenerator: #当你使用for进行迭代时,函数内的代码才执行
     print(i)
#3

print(next(mygenerator)) #或者使用next()
#3
def gen(max):         # def gen():
    a,b=1,1              # do something (a)
    while a<max:
        yield a          # yield a # (b)
        a,b=b,a+b        # do something (c)

for n in  gen(15):    # for n in gen():
    print(n)            # do something with n (d)

def dedupe(items): seen = set() for item in items: if item not in seen: yield item seen.add(item) a = [1, 5, 2, 1, 9, 1, 5, 10] for n in dedupe(a): print(n) 1 5 2 9 10

执行大致过程(个人理解,有误请指正):
        seen=set( )    生成器环境初始化
            | dedupe内的for循环 | 调用yield item=1 | print 1 | add 1 | dedupe内的for循环 | 调用yield item=5 | print 5 | add 5 | dedupe内的for循环 | 调用yield item=2 | print 2 | add 2 | dedupe内的for循环('1'已经在items里所以不执行循环) | dedupe内的for循环 | 调用yield item=9 | ......
def gen():
    print('a')
    yield 1
    print('b')
    yield 2
    return 3

g=gen()

print(g)
#<generator object gen at 0x7f144845a308>

print(next(g))
#a
#1

print(next(g))
#b
#2

print(next(g))
#StopIteration: 3

 

3. 生成器作用场景

3.1  计数器的例子

# version-1
def counter():
    x=0
    while True:
        x+=1
        yield x

def inc(x):
    return next(x)

# version-2
def counter():
    x=0
    while True:
        x+=1
        yield x

def inc():
    c=counter()
    return lambda :next(c)

# version-3
def make_inc(): def counter(): x=0 while True: x+=1
            yield x c=counter() return lambda :next(c)  ## 为什么这里不直接 return next(c) ##
def make_inc():
    def counter():
        x=0
        while True:
            x+=1
            yield x
    c=counter()
    return lambda :next(c)
# return lambda:next(c)的运行结果
incr=make_inc()
print(id(incr()))  #取1
#8939648
print(id(incr()))  #取2
#8939680


def make_inc():
    def counter():
        x=0
        while True:
            x+=1
            yield x
    c=counter()
    return next(c)
# return next(c)的运行结果
print(id(make_inc()))  #取1
#8939648
print(id(make_inc()))  #取1
#8939648

3.2  解决递归问题 — 生成斐波那契数列的例子

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

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

1
1
2
3
5

3.3  协程 —— 生成器的高级用法

进程、线程 ——在内核态调度的
协程 ——在用户态调度(即用户自己写调度器)
    ——运行在一个线程之内,所以也被叫做轻量线程
    ——非抢占式调度
调度 ——简单的说就是由调度器来决定哪段代码占用cpu时间

协程在python3已经进入标准库了 ——asyncio
python3.
5 中加入了 asyn、 await延伸支持

 

参考文章

《Python yield使用浅析-廖雪峰(含通过yield实现文件读取的方法)》

《[译]python关键字yield的解释(*)》

《python中的惰性求值》