day4-生成器

列表生成式

定义

列表[0,1,2,3,4,5,6,7,8,9],需求是把列表中的每个元素乘以2,你是怎么实现的呢?

>>> [i*2 for i in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

上面的列表相当于如下代码生成的

a = []

for i in range(10):
   a.append(i*2)
print(a)
#输出
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

产生背景

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

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

用法:要创建一个generator,有很多种方式。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator

1.生成器的创建

# 每循环一次乘以2
>>> a = (i*2 for i in range(10))    #通过()产生一个生成器并把它赋给a,不需要开始把所有读到内存
>>> a
<generator object <genexpr> at 0x1054b02b0>


>>> a = (i+3 for i in range(10000))
>>> for i in a:  # 生成器只有在调用时才会生成相应的数据
print(i)
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Traceback (most recent call last):
  File "<pyshell#13>", line 2, in <module>
    print(i)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/idlelib/PyShell.py", line 1344, in write
    return self.shell.write(s, self.tags)
KeyboardInterrupt

生成器不支持像列表一样的切片和取值功能,因为它还没有生成和读到内存

>>> a[5]
Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    a[5]
TypeError: 'generator' object is not subscriptable

如果需要访问生成器n中的值,python2是通过next()方法去获得generator的下一个返回值,python3是通过__next__()去获得generator的下一个返回值:

# 只有一个__next__()方法
>>> a.__next__() 20 >>> a.__next__() 21 >>> a.__next__() 22 >>> a.__next__() 23 >>> a.__next__() #只记录当前位置 24

总结:

1.generator保存的是算法,每次调用next方法时,就会计算下一个元素的值,直到计算到最后一个元素,如果没有更多元素,则会抛出StopIteration的错误。

2.generator只记住当前位置,它访问不到当前位置元素之前和之后的元素,之前的数据都没有了,只能往后访问元素,不能访问元素之前的元素。

 

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

函数来实现

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

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

 

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

def fib(max):
     n,a,b = 0,0,1  # 每循环一次就把a往后移动一次
     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
done

这边需要注意的是赋值语句:

a,b  = b,a+b

相当于:

t =( b,a+b)   # t是一个tuple
a=t[0]
b=t[1]

不必显式写出临时变量t就可以赋值

接着我们现在要将此函数变为一个生成器?

答:只需把print(b)变为yield b

def fib(max):
    n,a,b=0,0,1
    while n<max:
         yield b  #用yield替换print,把fib函数转化成一个生成器
         a,b=b,a+b
         n=n+1
    return"done"

print(fib(10))
#输出
<generator object fib at 0x102980888>

再使用__next__()方法访问生成器中的元素:

def fib(max):
    n,a,b=0,0,1
    while n<max:
        yield b  #把b返回到外面,所以就在这使用yield b(yield是保存了函数的中断状态)
        a,b=b,a+b
        n=n+1
    return"done"
x=(fib(10))
print(x.__next__())
print(x.__next__())
print(x.__next__())
print("===干点别的事=====")
print(x.__next__())   # next是唤醒yield
print(x.__next__())
print(x.__next__())
print(x.__next__())
print(x.__next__())
print(x.__next__())
print(x.__next__())
print(x.__next__())
print(x.__next__())
#输出
1
1
2
===干点别的事=====
3
5
8
13
21
34
55
Traceback (most recent call last):
  File "/Users/huwei/PycharmProjects/s14/module_2/斐波那契.py", line 21, in <module>
    print(x.__next__())
StopIteration: done

当打印的次数多过循环的次数时,抛出异常,并输出return 后面的语句,我们可以通过异常处理进行处理,使程序运行不会报错,那么

x=(fib(10))

while True:
 try:
   print(x.__next__())
   print(x.__next__())
   print(x.__next__())
   print("===干点别的事=====")
   print(x.__next__())
   print(x.__next__())
   print(x.__next__())
   print(x.__next__())
   print(x.__next__())
   print(x.__next__())
   print(x.__next__())
   print(x.__next__())
 except StopIteration as e:
   print("生成器返回值:",e.value)
   break
#输出
1
1
2
===干点别的事=====
3
5
8
13
21
34
55
生成器返回值:done

到现在已经不称作函数了,现在已经是一个生成器,所以在异常的时候,rerurn是返回的是打印异常的消息。

我们也可以使用for循环进行打印,超过循环最大次数,不会出现异常或报错:

x=(fib(10))

print(x.__next__())
print(x.__next__())
print(x.__next__())
print("===干点别的事=====")
print(x.__next__())
print(x.__next__())
print(x.__next__())
print(x.__next__())
print(x.__next__())
print("===开始循环=====")
For i in x:
   print(i)
#输出
1
1
2
===干点别的事=====
3
5
8
13
21
===开始循环=====
34
55

上面的列子都是从上往下执行,下面必须等待上面执行完,下面程序才能执行,对于这种单线程,只能这样

posted @ 2017-07-21 16:57  Mr.hu  阅读(105)  评论(0编辑  收藏  举报