列表生成式是一项非常Pythonic的编程技巧,其可以使复杂的逻辑代码变得异常简洁,也算得上是如今编程语言中较有特点的一项功能,如[x ** x for x in range(1,10) if x%2==0]的结果为[4, 256, 46656, 16777216]
如果将上面的方括号改为圆括号,即(x ** x for x in range(1,10) if x%2==0),得到的不再是一个列表了,而是一个生成器<generator object <genexpr> at 0x000001DA09220C00>。生成器与列表生成式的不同之处在于,生成器是惰性的,在列表生成式中,一旦语句被执行列表生成式就直接按所给逻辑生成了列表,而生成器则需要调用next()或send()方法才会主动生成下一个元素。由此可见,生成器十分适用于数据冗余较大的场合,例如只需要列表的前几个元素,自然没有必要将所有元素都算出来。
生成器本身是Iterator,而不像列表只停留在Iterable:

>>> from collections import Iterable

Warning (from warnings module):
  File "__main__", line 1
DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
>>> a = (x for x in range(1,4))
>>> b = [1,2,3]
>>> isinstance(a,Iterable)
True
>>> isinstance(b,Iterable)
True
>>> from collections import Iterator
>>> isinstance(a, Iterator)  # Iterator对象一律Iterable
True
>>> isinstance(b, Iterator)
False
>>> 

事实上,Python社区也注意到了这个问题:在Python2中range()返回的是列表,而xrange()返回的是在迭代层面上与列表效果一致的生成器。所以Python3中移除了xrange()生成器,range()改为了生成器。

获取一个生成器除了改写列表生成式外,包含yield关键字的函数也不能再像普通函数那样被调用,而成为了生成器:

>>> def yields(start,end):
    for i in range(start,end):
        yield i
        print(i)
        
>>> yields(1,3) # 若是普通函数,此处应该打印内容
<generator object yields at 0x000001DA09220C78>

在对yield没有进一步了解之前,将yield理解为函数中的return可能会帮助你理解yield的含义。

Iterator对象的特点就是除了像传统Iterable的对象一样可以在for循环中被迭代之外,还可以使用以下两类方法(以对象g为例):

  • next(g) 或g.__next__()
  • g.send(args)

对一个列表生成式改写的生成器来说,next()方法将逐个按要求计算需要的下一个元素,并在迭代完成后抛出StopIteration错误。

而对带yield关键字的函数式生成器而言,next()方法将运行至下一个yield处并将yield右值返回,等待下一次next()。

敏感的读者已经发现:Python2.5之后yield不再是语句,而是表达式(Expression),所以会有左值右值的概念。

yield让生成器更加灵活,例如你不可能生成一个包含全体正整数的列表,却可以用生成器实现一个可以逐个返回每个正整数的迭代器:

>>> def allPositiveNumbers():
    n = 0
    while True:
        n+=1
        yield n
>>> g = allPositiveNumbers()
>>> for i in range(100):
    print(next(g))
    
1
2
3
4
5
...
100

send(args)方法继承了next()方法,并在此基础上将args强行赋给了整个yield表达式,让生成器继续运行至下一个yield处。若生成器运行至末尾将抛出StopIteration错误。下面这个例子能够帮助大家理解:

>>> def helloworld():
    m = yield 2
    print(m)
    n = yield 3
    print(n)

    
>>> g = helloworld()
>>> next(g)
2
>>> g.send(4)
4
3
>>> g.send(8)
8
Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    g.send(8)
StopIteration
>>> 

需要注意的是:生成器的第一次迭代只能使用send(None)或者next()方法,而不能传递任何非None的参数,因为此时并没有yield表达式接受传递的参数,而抛出TypeError: can't send non-None value to a just-started generator错误,且对于send(None)方法,解释器底层也会将其视为next()执行。以下是官方解释:

Resumes the execution and “sends” a value into the generator function. The value argument becomes the result of the current yield expression. The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value. When send() is called to start the generator, it must be called with None as the argument, because there is no yield expression that could receive the value.

总结:Python中的生成器对主用其他语言的程序员来说可能并非必要,但入乡随俗,既然选择了Python,更加Pythonic的实现方式也未尝不可,值得各位掌握。
Python3.3后又有了yield from的关于yield的用法,今后开文介绍。

Last modification:July 28th, 2019 at 03:50 pm
If you think my article is useful to you, please feel free to appreciate