说生成器之前先说一个列表生成式:[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],
要注意生成器是保存着算法,不取值的时候不要去计算值。

 

posted on 2018-11-08 20:48  ~泪小白~  阅读(235)  评论(0编辑  收藏  举报