Python的生成器表达式与生成器函数

时间:2021-05-02 18:49:47

有一种特殊的迭代器, 叫做生成器. 生成器有两种, 生成器表达式与生成器函数.

生成器表达式

生成器表达式与列表推导在语法上十分相似:

  • 列表推导使用[]: [i for i in arr]
  • 生成器表达式使用(): (i for i in arr)

但是它们有着本质的不同: 列表推导在被创建时会为每个元素分配内存空间, 最后得到的是一个正常完整的list对象. 而生成器表达式在被创建时并不会为它的元素分配内存空间, 因为它的元素此时还没有生成, 要等到被访问时才会生成并进入内存空间, 即以on the fly方式生成元素.
所以, 生成器与列表推导相比, 优势是更省内存, 但不支持常见len, arr[idx]等这些常见的序列操作.

a = range(10)
list_comp = [i for i in a]
gen_exp = (i for i in a)
print '列表推导得到一个list对象:', type(list_comp)
print '生成器表达式得到一个生成器对象:', type(gen_exp)
列表推导得到一个list对象: <type 'list'>
生成器表达式得到一个生成器对象: <type 'generator'>
print '生成器不能使用len方法:'
len(gen_exp)
生成器不能使用len方法:



---------------------------------------------------------------------------

TypeError Traceback (most recent call last)

<ipython-input-3-ef3f80c9ff21> in <module>()
1 print '生成器不能使用len方法:'
----> 2 len(gen_exp)


TypeError: object of type 'generator' has no len()
print '生成器不能按索引访问:'
gen_exp[0]
生成器不能按索引访问:



---------------------------------------------------------------------------

TypeError Traceback (most recent call last)

<ipython-input-4-b9810026b625> in <module>()
1 print '生成器不能按索引访问:'
----> 2 gen_exp[0]


TypeError: 'generator' object has no attribute '__getitem__'
print '生成器只支持顺序访问, 每个元素只能访问一次:'
print '第一次访问:'
for i in gen_exp:
print i
print '第二次访问:'
for i in gen_exp:
print i
生成器只支持顺序访问, 每个元素只能访问一次:
第一次访问:
0
1
2
3
4
5
6
7
8
9
第二次访问:

生成器函数

含有yield关键字的函数就是生成器函数, 它有以下几个特点:

  1. 含有yield关键字, 没有return关键字, 或者空return
  2. 调用生成器函数会返回一个生成器
  3. 调用生成器函数时, 它包含的代码并不会马上执行, 而是等到访问生成器元素时才一段一段地执行.

下面通过示例来逐点说明.

print '有yield时, 通过return返回值会报错:'
def gen_func():
yield 1
yield 2
yield 3
return 3
有yield时, 通过return返回值会报错:



File "<ipython-input-6-b64b918a6045>", line 6
return 3
SyntaxError: 'return' with argument inside generator
print '可以使用空的return:'
def gen_func():
yield 1
yield 2
yield 3
return
print gen_func
print gen_func()
可以使用空的return:
<function gen_func at 0x7f713c086d70>
<generator object gen_func at 0x7f713c0825f0>
print '也可以不使用return:'
def gen_func():
yield 1
yield 2
yield 3

print gen_func
print gen_func()
也可以不使用return:
<function gen_func at 0x7f713c086938>
<generator object gen_func at 0x7f713c082460>

从上面的输出可以看出, 生成器函数本身是一个函数对象, 但它的返回值是一个生成器对象. 所以, 生成器函数是一个generator factory, yield则是一种特殊的return.

def gen_func():
print 'yield 1...'
yield 1
print 'yield 2...'
yield 2
print 'yield 3...'
yield 3

print '调用gen_func时, 不会print任何东西, 因为函数体内的代码不会被执行:'
gen = gen_func()
print '调用gen_func结束.'

print '访问生成器内的元素时才会执行函数体内的代码:'
print '开始访问生成器的元素:'
for i in gen:
print i
print '访问生成器的元素结束.'
调用gen_func时, 不会print任何东西, 因为函数体内的代码不会被执行:
调用gen_func结束.
访问生成器内的元素时才会执行函数体内的代码:
开始访问生成器的元素:
yield 1...
1
yield 2...
2
yield 3...
3
访问生成器的元素结束.

同样的, 通过生成器函数生成的生成器也不支持len[], 元素也只能访问一次. 每访问一次它的元素, 它就从上次停止执行的位置继续执行函数代码直到遇到下一个yield并返回yield后面的值.

总结

  1. 生成器:

    • 一种特殊的迭代器
    • 以on the fly的方式生成元素
    • 生成的元素只能访问一次
  2. 生成表达式: (exp(i) for i in iterable)

  3. 生成器函数:
    • 包含yield, 没有return
    • 调用函数时, 直接返回一个生成器, 其函数体内的代码不会被执行, 访问生成器的元素时才会被执行.
  4. 使用生成器的场景: 有大量顺序访问的元素, 但所有元素只访问一次. 这时使用生成器可以节省大量内存空间.