Python


305 浏览 5 years, 9 months

17.10 列表生成器 (list generator)

版权声明: 转载请注明出处 http://www.codingsoho.com/

列表生成器 (list generator)

生成器 generator

不一次性生成所有的列表单元, 一边循环一边计算列表单元, 适合可以推算或有通项的算法, 可以从开始有”有限个”单元推算出后面所有的单元

调用列表生成器返回一个generator对象

格式

exp for iter_var in iterable if exp1(formula for 循环)

特点

元素没有生成,所以不能直接使用

  • 用for循环来获取所有的元素, 不会出现StopInteration 错误
  • next(list)函数获取生成的下一个元素(很少用到此方法), 当没有更多的元素时,抛出StopInteration错误

其它特点同列表生成式

>>> L = [x*x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x*x for x in range(10))
>>> g
<generator object <genexpr> at 0x0000000000B6A410>
>>> for n in g:
...     print(n)
...
0
1
4
9
16
25
36
49
64
81
>>>
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

g生成的generator,不能直接使用。

>>> g = (x*x for x in range(10))
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

通过next可以获得下一个元素,超出最后一个后会抛出错误。

自定义generator (用yield语句定义列表生成器)

如果一个函数中含有yield关键字,它就是一个列表生成器

generator和函数的执行流程不一样

  • 函数是顺序执行,遇到return或最后一行语句就返回。
  • 列表生成器在每次调用next()时执行,遇到yield语句就挂起暂停
    • yield在这里可以保留fib函数的计算现场,暂停fib的计算并将b返回给调用者
    • 再次执行时从上次返回的yield 语句处继续执行
    • 如何得到自定义列表生成器的返回值?
  • send函数向生成器发送一个值,并触发生成器继续执行,next()相当于send(None)
  • 同样可以用for循环来迭代
代码示例

示例:斐波拉切数列

# 函数实现
>>> def fib(max): 
...     n,a,b = 0,0,1
...     while n < max:
...         print(b,end="")
...         a,b = b,a+b
...         n=n+1
...     return 'done'
...
>>> fib(6)
112358'done'

函数实现,但是效率非常低

>>> # 列表生成器实现
... def fib_g(max):
...     n,a,b = 0,0,1
...     while n < max:
...         send_value = yield b # 列表生成器语句,返回b值
...         a,b = b,a+b
...         n = n+1
...     return 'done'
...
>>>
>>> g = fib_g(6)
>>> print(next(g),next(g),next(g)) # next触发下一个值
1 1 2
>>> print(g.send(1),g.send(1),g.send(1)) # 使用send触发下一个值
3 5 8
>>> g = fib_g(6)
>>> while True:
...     try:
...         x = next(g)
...         print(x,end=" ")
...     except StopIteration as e:
...         print('generator return value', e.value)
...         break
...
1 1 2 3 5 8 generator return value done
>>> g = fib_g(6)
>>> for x in g:
...     print(x, end=" ")
...
1 1 2 3 5 8 >>>
def fib(max): # 函数实现
    n,a,b = 0,0,1
    while n < max:
        print(b,end="")
        a,b = b,a+b
        n=n+1
    return 'done'

# 列表生成器实现
def fib_g(max):
    n,a,b = 0,0,1
    while n < max:
        send_value = yield b
        a,b = b,a+b
        n = n+1
    return 'done'

g = fib_g(6)
print(next(g),next(g),next(g))
print(g.send(1),g.send(1),g.send(1))

g = fib_g(6)
while True:
    try:
        x = next(g)
        print(x,end=" ")
    except StopIteration as e:
        print('generator return value', e.value)
        break

g = fib_g(6)
for x in g:
    print(x, end=" ")

多个yield实例

迭代器可以有多个yield输出不同的结果

迭代器停止和输入异常 - 可以通过send异常到迭代器实现一些需要的功能

def dog():
    while True:
        try:
            try:
                food = yield                    # stop once, use next or send(None) to continue
                if food is not None:
                    if food == '':
                        yield u'Nothing to eat' # stop once
                    elif food == u'bone':
                        yield u'delicious'      # stop once
                    elif food == u'vegetable':
                        yield u'dont like'      # stop once
                    else:
                        yield u'what is it?'    # stop once
                else:
                    yield 'no food'             # stop once
            except ValueError:
                yield u'unhappy'                # stop once
            except GeneratorExit as e:
                print(u'bye bye')
                raise StopIteration             # need raise exception, otherwise it will not stop
                # 此异常需要在外围捕获,本层捕获不了
        except StopIteration:
            print(u'zzz')
            break
d = dog(); next(d)                      # 启动迭代器,或用send(None) # 停在第一个yield
print(d.send(''))                       # 从第一个yield继续,停在第二个yield
next(d)                                 # 从第二个yield继续,停在第一个yield
print(d.send('bone'))                   # 从第一个yield继续,停在第二个yield
next(d)                                 # go on, 从第二个yield继续,停在第一个yield
print(d.send('carrot'));next(d)         # 从第一个yield继续,停在第二个yield
print(d.throw(ValueError))              # 从第二个yield继续,停在第一个yield
next(d)                                 # 从第一个yield继续,停在第二个yield
d.close()                               # 停止迭代器,会产生GeneratorExit异常
next(d)                                 # 生成器已停止,抛出StopIteration异常,并被外围try捕获

执行结果

>>> d = dog(); print(next(d))
None
>>> print(d.send(''))
Nothing to eat
>>> print(next(d))
None
>>> print(d.send('bone'))
delicious
>>> print(next(d))
None
>>> print(d.send('carrot'));print(next(d))
what is it?
None
>>> print(d.throw(ValueError))
unhappy
>>> print(next(d))
None
>>> d.close()
bye bye
zzz
>>> print(next(d))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

杨辉三角练习

定义一个列表生成器triangles(),生成杨辉三角, 期待输出如右所示

# 杨辉三角列表生成器定义
def triangles():
    ret = [1]
    while len(ret)-1 < 10:
        yield ret
        ret = [ret[i] + ret[i+1] for i in range(len(ret)-1)]
        ret.insert(0,1);
        ret.append(1)
# 测试代码
n = 0; results = []
for t in triangles():
    print(t); results.append(t); n=n+1
    if n == 10:
        break

if results == [
    [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]]:
    print("test pass")
else:
    print("test fail")