Python有个yield语法,这种语法叫做生成器.例如:

In [1]:
def test(N):
    for i in range(N):
        yield i ** 2
In [2]:
test(5)
Out[2]:
<generator object test at 0x7f6d8009d620>

我们可以使用for循环迭代它:

In [4]:
for item in test(5):
    print(item)
0
1
4
9
16

不使用yeild的实现方案:

In [5]:
def test(N):
    res = []
    for i in range(N):
        res.append(i*i)
    return res

for item in test(5):
    print(item)
0
1
4
9
16

首先你会发现生成器的第一个优点,它会使代码变得简洁,清晰.

其次,不使用yield语句函数会将列表一次性返回,而使用yield语句一次只返回一个结果,然后挂起函数的状态,下次从离开的地方继续执行.假如列表很大的话,返回整个列表会占用很大内存,而生成器用到哪个值的时候现去计算,比较节约内存,这在处理大数据的时候非常有优势.这是生成器的第二个优点.

除了yield语法外,Python还可以以表达式的形式建立生成器.先来看通常建立列表的方法:

In [6]:
squares = [x**2 for x in range(5)]
squares
Out[6]:
[0, 1, 4, 9, 16]

将列表推导的中括号,替换成圆括号,就是一个生成器表达式:

In [7]:
squares = (x**2 for x in range(5))
squares
Out[7]:
<generator object <genexpr> at 0x7f6d6b784728>

和使用yield语法是相同,没有直接返回列表,而是返回一个生成器对象,当我们迭代的时候才会去计算其中的值.

另外,生成器实现了迭代器协议,所以可以使用next()函数调用下一个值:

In [8]:
next(squares)
Out[8]:
0
In [9]:
next(squares)
Out[9]:
1
In [10]:
next(squares)
Out[10]:
4

Python的大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:

In [11]:
sum(x ** 2 for x in range(4)) 
Out[11]:
14

而不用多此一举的先构造一个列表:

In [12]:
sum([x ** 2 for x in range(4)]) 
Out[12]:
14

最后,让我们用一个例子来更好的理解生成器延迟计算带来的强悍效果.

In [ ]:
sum([i for i in range(10000000000)])
In [ ]:
sum(i for i in range(10000000000))

对于第一个表达式,你会发现内存几乎接近饱和,对于第二个表达式,内存几乎没有什么消耗.有了生成器再也不用担心内存不够用了有木有.

posted @ 2019-01-26 21:54:06
评论加载中...

发表评论