大聊PYthon----生成器

再说迭代器与生成器之前,先说一说列表生成式

列表生成式

什么是列表生成式呢?

这个非常简单!

先看看普通青年版的!

>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> b = []
>>> for i in a:b.append(i+1)
... 
>>> b
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = b
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

再看看原值修改版的!

a = [1,3,4,6,7,7,8,9,11]

for index,i in enumerate(a):
    a[index] +=1
print(a)

再看看文艺青年版的!

>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = map(lambda x:x+1, a)
>>> a
<map object at 0x101d2c630>
>>> for i in a:print(i)
... 
2
3
4
5
6
7
8
9
10
11

再看看装逼青年

>>> a = [i+1 for i in range(10)]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

这就叫做列表生成

列表生成的主要作用使代码更简洁
说完了列表生成式,那现在就开始说说生成器


生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。(说白了就是浪费内存

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

生成器的机制

a、生成器只有在调用的时候才会生成相应的数据.

先看一段代码:

>>> Good_man = [i*2 for i in range(100)] 
>>> Good_man
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72
, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134
, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190,
192, 194, 196, 198]

在路上你遇到了一个好人,他说:我能把你手里的100块钱变成200块,只要你给我,我就给你变!于是你就给他100块,很快的时间,他就把变的200块钱给你了,也没有贪图你一分钱!

再看一段代码:

>>> Bad_man = (i*2 for i in range(100))
>>> Bad_man
<generator object <genexpr> at 0x000000000066BE08> # 生成器
>>>

遇到好人之后,他将100块变成了200块,于是你的心理美滋滋,想着如果再能碰到这样的人就好,当你走着走着,突然看到一个很道貌盎然的男人,这个男人在路边看到了一个好人给他变钱,于是这个男人想把你的钱骗走,于是他就跟你说,他也能把100变为200,于是你就把钱给他了,但是他没有变出来,不是他不会变,而是他不想变,你看到他没给你钱于是你想朝他要 ,你先朝他要2块钱,他也就只给你两块钱,你想要10块,他只给你10块,多一分钱都不会给你,他就是这么吝啬!他不会一起把钱全部给你,只会两块两块的给你,直到把200块钱都给你,看下面代码!

>>> Bad_man = (i*2 for i in range(100))
>>> for i in Bad_man:
...     print(i)
...
0
2
4
·
·
·
·
·
194
196
198

b、只记录当前位置,只有一个__next__()方法.(在python2.7中是next( ))

Bad_man = (I*2 for i in range(1000000)
for i in Bad_man:
    print(i)

#输出
0
2
4
6
8
·
·
·
·
147946  # 突然在这里发生了错误

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
KeyboardInterrupt
>>> ^X
  File "<stdin>", line 1
    
    ^
SyntaxError: invalid syntax

于是你想查找出现错误后的下一个数,通过

>>> Bad_man.__next__()
147948

找到了错误的一下个数,但是你想能不能找到出现错误的数的前几个代码呢?于是你就查找各种资料,结果发现不能,于是经过查找,发现,只能一个个的查找后面的数值,而且只能用一种__next__()方法,想查找前面的数值是不可能的了!

函数来实现生成器

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'
fib(10)

输出结果:

1
1
2
3
5
8
13
21
34
55

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'  # return的作用是 程序异常时打印出消息
print(fib(10))

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

输出结果:

<generator object fib at 0x00000000006DBD00>

怎么样?很眼熟吧!

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

f = fib(10)
print(f)
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print("下次玩的舒服点哦!")
print(f.__next__())
print(f.__next__())

# 输出
<generator object fib at 0x0000000000AD5D00>
1
1
2
3
下次玩的舒服点哦!
5
8
13

在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

f = fib(10)
print(f)
for n in f:
    print(n)
# 打印
<generator object fib at 0x0000000000665D00>
1
1
2
3
5
8
13
21
34
55

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

g = fib(10)
while True:
    try:
        x = next(g)
        print('g:', x)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break

# 输出
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
g: 13
g: 21
g: 34
g: 55
Generator return value: done

 

posted @ 2018-06-30 15:42  追风的小蚂蚁  阅读(258)  评论(0编辑  收藏  举报