说生成器之前先说一个列表生成式:[x for x in range(10)] ->[0,1,2....,9]这里x可以为函数(因为对python而言就是一个对象而已),range(10)也可以换成可迭代对象。
如果说有一天我们的数据量很大呢?range(10000000)甚至更大呢?那我们会挤爆内存的,所以我们需要用到生成器(生成器是特殊的迭代器,当然就是可迭代对象了),因为生成器不会将所有数据存在内存中,只是保存了算法,刚刚说到的[x for x in range(10)]实际上改成(x for x in range(10))就成了一个生成器(generator)对象,如下:
g = (x for x in range(5)) print(g) # 这里是生成器对象 print(next(g)) # 取值,每次取一个,跟g.__next__一样,在py2中用g.next() for i in g: # 循环取出剩余的值,为了增强效果所以加了# print("#"+str(i))
结果就是:
<generator object <genexpr> at 0x0000000001E0EFC0> 0 #1 #2 #3 #4
实际上这是生成器一种生成的方法(生成器表达式),一般我们调用的话也是用for循环,不会一个一个next,接着来看第二种生成器产生的方法,就是生成器函数,简单的来讲就是只要函数里面带yield就是生成器。
看一下下面的这个就是一个生成器:
def foo(): print("num1") yield 1 print("num2") yield 2 g = foo() # 此时g就是生成器 print(g) # <generator object foo at 0x000000000216EFC0> for i in g: print(i) ''' 下面是结果: <generator object foo at 0x0000000001E1EFC0> num1 1 num2 2 '''
还有比如这个是我们之前写过的斐波那契数列,正常是下面这种实现的。我们也可以通过yield实现
def feibo(max): n, before, after = 0, 0, 1 while max > n: print(after) before, after = after, before+after # 先算等号右边的,所以第一次右边是1,1,然后再赋值给左边 n += 1
下面就是yield版本:
def feibo(max): n, before, after = 0, 0, 1 while max > n: yield after before, after = after, before+after n += 1 g = feibo(5) print(g) for i in g: print(i) '''
#结果如下:
<generator object feibo at 0x0000000001DEEFC0> 1 1 2 3 5 '''
这里说了yield关键字可以暂停函数并且返回一个值,既然生成器可以返回值给外部,那么外部能不能传值给生成器呢?答案是当然可以的,可以通过send方法将值传入上一次被挂起的yield语句的返回值,当然这样说可能你一脸懵B,没事,举个例子,看下面:
def foo(): print("num1") value1 = yield 1 print(value1) print("num2") value2 = yield 2 print(value2) # 实际上每个函数最后面都有个return None,如果我们不写return的话 g = foo() # 这个是生成器对象 next(g) # 先跑到yield 1的位置,但是又yield所以在这里停住了,然后返回了1,这一步也可以用g.send(None) g.send("liu") # 遇到了send或者next就会继续返回上一次的位置,此时将"liu"赋值给了value1然后打印value1和num2,走到yield 2停住,返回2 g.send("kang") # 接着这里kang字符串赋值给value2,注意在一个生成器对象没有执行next方法之前,由于没有yield语句被挂起,所以执行send方法会报错,所以后面就直接报错了(碰到return也会报错)
执行结果如下:
num1 liu num2 kang Traceback (most recent call last): File "D:/BaiduNetdiskDownload/python/Python_code/week_04/generate_test.py", line 51, in <module> g.send("kang") StopIteration
现在应该对send的作用有所了解了吧,这里我们来做个小小练习,通过上述学到的生成器来简单写个伪并发边生产边消费的例子吧,可以先想一想然后再往下看:
""" 需求:需要实现生产者消费者伪并发 分析: 1、肯定是需要两个函数分别为生产者和消费者 2、生产者需要生产东西,然后再发生(send)给消费者 3、消费者吃完后需要停一下(yield) """ # 消费者 def comsumer(name): while True: food = yield print("%s eat %s" % (name, food)) # 生产者 def producer(name): c1 = comsumer("xiaoming") c2 = comsumer("xiaopang") next(c1) # 在一个生成器对象没有执行next方法之前,由于没有yield语句被挂起,所以不能直接就send值,可以send(None) next(c2) for i in range(1,10,2): print("%s在生产商品%s和%s" % (name, i, i+1)) c1.send(i) c2.send(i+1) producer("liu")
执行结果如下:
liu在生产商品1和2 xiaoming eat 1 xiaopang eat 2 liu在生产商品3和4 xiaoming eat 3 xiaopang eat 4 liu在生产商品5和6 xiaoming eat 5 xiaopang eat 6 liu在生产商品7和8 xiaoming eat 7 xiaopang eat 8 liu在生产商品9和10 xiaoming eat 9 xiaopang eat 10
总的来说,send方法和next方法唯一的区别是在执行send方法会首先把上一次挂起的yield语句的返回值通过参数设定,从而实现与生成器方法的交互。但是需要注意,在一个生成器对象没有执行next方法之前,由于没有yield语句被挂起,所以执行send方法会报错。
上面我们提到了三个概念,分别是生成器、迭代器还有可迭代对象,那这几个是啥关系呢?这个图中包含了容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)众多概念的关系,我感觉非常不错就借鉴过来了。
容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用 in , not in 关键字判断元素是否包含在容器中。
# 生成器都是迭代器,反之不成立 """ 1、可迭代对象都有__iter__()方法,而迭代器除了__iter__()方法还有__next__()方法 2、可迭代对象l = [1,2,3]可以通过iter(l)变成迭代器,这样就可以使用__next__()方法 """ l = [1,2,3] d = iter(l) """ for循环干的活: 1、调用可迭代对象的iter方法返回迭代器对象 2、循环调用迭代器对象的next方法 3、处理StopIteration异常 """ for i in d: print(i) from collections import Iterator,Iterable print(isinstance(l, Iterable)) # 判断l是否为可迭代对象 True print(isinstance(d, Iterable)) # 判断d是否为可迭代对象(迭代器一定是可迭代对象,因为一定有iter方法) True print(isinstance(l, Iterator)) # 判断l是否为迭代器对象 False print(isinstance(d, Iterator)) # 判断d是否为迭代器对象 True with open("qq") as f: print(f.__next__()) print(isinstance(f, Iterator)) # True f是迭代器对象 """ 练习1:使用文件读取,找出文件最长的长度 分析: 1、肯定是要先打开文件 2、长度的话肯定需要len方法,最长的可以用max """ 答案:max(len(x.strip()) for x in open("qq"))
下面还有个小练习,是针对生成器的理解
def add(s, x): return s + x def gen(): for i in range(4): yield i base = gen() for n in [1, 10]: base = (add(i, n) for i in base) print list(base)
解析:
for n in [1, 10]: base = (add(i, n) for i in base)
实际上最后在list(base)的时候才最终将值全部取出来了,前面一直都是保存着算法而已,所以对于第二次就是base = (add(i, n) for i in (add(i, n) for i in g)),所以到10的时候就直接讲10带入了,所以结果就是[20, 21, 22, 23],
要注意生成器是保存着算法,不取值的时候不要去计算值。