[python]Generators

Posted on 2017-02-07 14:25  你猜我猜不猜你猜不猜  阅读(260)  评论(0)    收藏  举报

generators(生成器)是python提供的一种机制,可以让函数一边循环一边计算,通常函数是一遍执行,而生成器可以在执行中间交出变量,下次调用时从交出变量的地方重新开始,这种机制通过yield关键字实现。

考虑生成斐波那契数,即1,1,2,3,5,8......、

假设我不指定生成的项数,而通过调用next()的方法不断循环下一个数,那么代码应该如下:

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 
    def __iter__(self):
        return self 
    def next(self):
        self.a, self.b = self.b, self.a + self.b 
        if self.a > 100000: 
            raise StopIteration()
        return self.a

实际上这么编写的Fib类已经实现了迭代器(Iterator)的功能,因为实现了__iter__()和next()方法,在一个实例中调用next()方法可以不断的得到下一个斐波拉契数,而用for循环时则会先调用__iter__()方法,该方法返回一个实例,再去调用该实例的next()方法。

其实这里好像已经实现不断迭代的功能了(笑),然而我们一开始是要用生成器来实现的,那生成器应该怎么做。

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

如此就编写了一个生成器,代码真是精简(捂脸)......

同样的,a=fib()后,a可以调用next()方法,并且a也可以用for循环,结果与迭代器一致。

注意这里如果直接调用fib().next()则每次输出都是1,1,1,1,1,1

打印一下fib().next看看

fib().next
<method-wrapper 'next' of generator object at 0x0000000003AEFEE8>
fib().next
<method-wrapper 'next' of generator object at 0x0000000003B553F0>

可以看到两次的地址不一致,其实就是创建了一个方法而已,再看看fib跟fib()的区别:

fib
<function fib at 0x0000000003B4FAC8>
fib()
<generator object fib at 0x0000000003AEFD38>

一个是函数,一个是生成器。

因此a=fib是指向了fib方法,a=fib()才是”继承“了一个生成器。

 

在廖雪松的Python教程中生成器一节有一个杨辉三角练习

def triangles():
    L=[1]
    while(True):
        yield L
        L.append(0)
        L = [L[i-1]+L[i] for i in range(len(L))]
n=0
for t in triangles():
    print(t)
    n = n + 1
    if n == 10:
        break

打印结果为:

[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

这段代码没有难度,不过里面用到的一个小技巧就是L[-1]+L[0]中L[-1]其实是L的最后一个元素,也就是新添加的0,这样十分巧妙地将需要往L中首尾添加0的情形变为只要末尾添加0就行了,十分的耐人寻味。

 

yield的用法其实不止体现在生成器中,在Python的上下文管理中也有体现。例如某个过程的上下文管理如下:

from contextlib import contextmanager
@contextmanager
def context():
    print "Begin"
    yield
    print "End"
    
with context():
    print "a process"

因为context()实现了上下文管理,所以可以用with来调用,当执行倒yield时,函数退出交给实际的process处理,完成后再继续调用yield后面的语句结束。输出结果为:

Begin
a process
End

 

总结:

Python中生成器的编写主要是用到了yield关键字,让函数能够”停下来“一次一次地输出,迭代器能实现同样的功能,但迭代器要实现__iter__()和next()方法。

 

http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014317799226173f45ce40636141b6abc8424e12b5fb27000

http://blog.csdn.net/scelong/article/details/6969276

博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3