说生成器之前先说一个列表生成式:[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
#
#
#
#
实际上这是生成器一种生成的方法(生成器表达式),一般我们调用的话也是用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],
要注意生成器是保存着算法,不取值的时候不要去计算值。