python生成器

生成器(generator)指代的是生成器对象,它可以由生成器表达式得到,也可以使用yield关键字得到一个生成器函数,调用这个生成器函数就可以得到一个生成器对象。另外,通过数据的转换也可以获取生成器。

生成器对象是一个可迭代对象,它是一个迭代器。生成器的作用是延迟计算,惰性求值。

生成器表达式产生生成器对象

#生成器表达式产生生成器
a =(x**2 for x in range(5))
print(type(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))

结果为:
<class 'generator'>
0
1
4
9

生成器函数

生成器函数也就是函数体中包含yield关键字的函数,这个函数会返回一个生成器对象!

def inc():
    for i in range(5):
        yield i 

print(type(inc))
print(type(inc()))

x=inc()
print(type(x))
print(next(x))

for m in x:
    print(m,"**")

for i in x:#因为x为生成器对象,所以它只能被遍历一次。再次遍历不会被执行了。
    print(i,"*")

结果为:

<class 'function'>
<class 'generator'>
<class 'generator'>
0
1 **
2 **
3 **
4 **

由上面的例子可以发现,普通的函数调用,函数会立即执行完毕,但是生成器函数不会,它可以使用next函数执行多次,直到最后。

def func():
    print("111")
    yield 222
gener = func() 
ret = gener.__next__()
#print(ret)

结果为:
111

而生成器函数等价于生成器表达式,只不过生成器函数可以更加的复杂。比如上面的生成器函数可以写成下面的生成器表达式。

y = (i for i in range(5))
print(type(y))
print(next(y))
print(next(y))

结果为:
<class 'generator'>
0
1

在生成器函数中,可以使用多个yield语句,执行一次后会暂停执行,然后把yield表达式的值返回。函数如果再次执行的话,会执行到下一个yield语句,在生成器函数中, return依然可以终止程序的运行,单return语句的返回值不能被获取到。

在生成器函数中,return会导致无法继续获得下一个值,这个时候会抛出stopiteration异常,而如果函数没有显示的return语句,如果生成器函数执行到结尾,一样会抛出stopiteration异常。

def gen():
    print("line 1 ")
    yield 1
    print("line 2")
    yield 2
    print("line 3")
    return 3

next(gen())#line 1
next(gen())#line 1都是line1因为这不是同一个生成器对象,第二次执行函数,又返回了一个生成器对象。

g =gen()
print(next(g))
print(next(g))
print(next(g))

结果为:
line 1 
line 1 
line 1 
1
line 2
2
line 3
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-16-3c0007202311> in <module>
     13 print(next(g))
     14 print(next(g))
---> 15 print(next(g))

StopIteration: 3


def gen():
    print("line 1 ")
    yield 1
    print("line 2")
    yield 2
    print("line 3")
    return 3

next(gen())
next(gen())

g =gen()
print(next(g))
print(next(g))
print(next(g, 'End')) #没有元素了,给个缺省值
print(next(g, 'End')) 

结果为:

line 1 
line 1 
line 1 
1
line 2
2
line 3
End
End

所以,生成器函数就是包含yield语句的函数,而生成器函数生成“生成器”的时候,生成器函数的函数体并不会立即执行,它需要next函数,有点拨一下转一下的意思,next(generator)会从函数的当前位置向后执行到碰到的第一个yield语句,这个时候有值会弹出值,并且暂停函数的执行。

再次调用next函数,重复上面的步骤。,而如果没有多余的yield语句被执行,如果再次调用next函数,就会抛出stopiteration异常。

生成器应用

现在有一个需求,比如我像一个服装厂订购了100件衣服,这个时候服装厂全部给我生成好了。比如下面这个代码。

def cloth():
    lst = []
    for i in range(10):
        lst.append("cloth"+str(i))
    return lst
cloth()

结果为:
['cloth0',
 'cloth1',
 'cloth2',
 'cloth3',
 'cloth4',
 'cloth5',
 'cloth6',
 'cloth7',
 'cloth8',
 'cloth9']

但是,现在我还没有那么多的学习,一次性给我那么多,我根本没法用。所以,最好的我要一件,你给我一件。这个时候就需要用到生成器了。

def cloth():
    lst = []
    for i in range(10):
         yield "cloth"+str(i)

a = cloth()
print(a.__next__())
print(a.__next__())
print(a.__next__())

结果为:
cloth0
cloth1
cloth2

所以,上面的第一种就是一次性全部拿出来,会占用内存,第二种生成器,一次就一个,用多少生成多少,用next函数一个一个的拿,然后指向下一个。

生成器还可以用在诸如无限循环,计算器,处理递归等问题上,应用最广泛的是协程。

#生成器处理无限循环,就不会陷入死循环,它拨一下,转一下。
def counter():
    i = 0
    while True:
        i+=1
        yield i

def inc(a):
    return next(a)

c = counter()
print(inc(c))
print(inc(c))
for i in range(10):
    print(inc(c))

结果为:

1
2
3
4
5
6
7
8
9
10
11
12

应该注意的是,如果上面的代码写成下面这样,每次就会产生一个新的“生成器”,达不到拨一下转一下的效果。

def counter():
    i = 0
    while True:
        i+=1
        yield i

def inc():
    a = counter()
    return next(a)

print(inc())
print(inc())
print(inc())

结果为:
1
1
1

下面用生成器配合无限循环写一个计数器。

def inc():
    def counter():
        i = 0
        while True:
            i+=2
            yield i
    c = counter()
    return lambda :next(c)

foo = inc()
print(foo())
print(foo())

结果为:
2
4

这个例子和上面无限循环差不多,都是一个意思,只是换了一种写法,同时这个例子用到了lambda表达式,而return返回的是一个匿名函数。它也等价于下面的代码:

def inc():
    def counter():
        i = 0
        while True:
            i+=2
            yield i
    c = counter()
    def _inct():
        return next(c)
    return _inct

foo = inc()
print(foo())
print(foo())
print(foo())

生成器同样可以用来处理递归的问题,比如下面这个例子实现斐波拉契数列。可以想生成多少就生成多少。

def fib():
    x = 0
    y = 1
    while True:
        yield y
        x,y=y,x+y

foo = fib()
for _ in range(5):#生成5个
    print(next(foo))

for _ in range(5):#再生成5个
    print(next(foo))

结果为:

1
1
2
3
5
8
13
21
34
55

上面的这段代码等价于下面的这段代码。

pre = 0 
cur = 1
print(pre,cur,end=" ")

def fib1(n,pre = 0,cur=1):
    pre,cur=cur,pre+cur
    print(cur,end=" ")
    if n ==2:
        return
    fib1(n-1,pre,cur)
fib1(11)

结果为:
0 1 1 2 3 5 8 13 21 34 55 89 

生成器还有一个高级用法,那就是协程coroutine,协程比进程、线程轻量级,它是在用户空间调度函数的一种实现,在Python 3 中,asyncio就是协程实现,已经加入到标准库,同时在Python3.5中,使用async\await关键字直接原生支持协程。

协程调度器实现原理为:有两个生成器A,B,next(a)后,A执行到了yield语句暂停,然后去执行next(B),B执行到yield语句也暂停,然后再次调用next(A),再调用next(B),周而复始,就实现了调度的效果。

同时应该注意,协程是一种非抢占式的调度。

yield from

yield fromީ是Python3.3出现的新语法,yield from iteratle 是 for item in iterable:yield item形式的语法糖。

def inc():
    for i in range(1000):
        yield i
foo = inc()
print(next(foo))
print(next(foo))
print(next(foo))


def inc():
    yield from range(1000)
foo = inc()
print(next(foo))
print(next(foo))
print(next(foo))

结果都为:
0
1
2

上面的两端代码其实是等效的。

再比如下面这个例子,从一个可迭代对象中一个个拿元素。

def counter(n): 
    for x in range(n):
        yield x

def inc(n):
    yield from counter(n)

foo = inc(10)
print(next(foo))
print(next(foo))
print(next(foo))

结果为:
0
1
2

 

 
 

posted on 2019-10-17 23:02  xpc199151  阅读(167)  评论(0编辑  收藏  举报

导航