python学习:函数---生成器、生成器表达式、推导式

一、生成器的几个特点

1、含有yield关键字的函数都是生成器函数。

2、yield和return不能共存。

3、return一般一个函数中只有一个,它会将函数终止,并返回一个值给调用函数的地方。

4、yield在一个函数中可以有多个,它不会终止函数,下次还可以接着从离开它的地方继续执行。使用next()或者__next__()可以获取到yield的生成的元素。

5、生成器函数被调用后,会返回一个生成器。调用时,并不会执行生成器函数内部的代码,如下图,在调用next(g)或者g.__next__()方法的时候才会执行函数内部的代码,遇到yield关键字就不再继续往下执行了。

6、使用生成器的好处就是,可以避免一下子在内存中产生过多的数据,只是在我们需要的时候生成一部分数据就可以了。

7、yield和next是一一对应的,如果next超过了yield的数量,程序就会报错。

 1 def produce():
 2     """生产衣服"""
 3     for i in range(2000000):
 4         yield "生产了第%s件衣服"%i
 5 
 6 product_g = produce()
 7 print(product_g.__next__()) #要一件衣服
 8 print(product_g.__next__()) #再要一件衣服
 9 print(product_g.__next__()) #再要一件衣服
10 num = 0
11 for i in product_g:         #要一批衣服,比如5件
12     print(i)
13     num +=1
14     if num == 5:
15         break
16         
17 # 运行结果:
18 生产了第0件衣服
19 生产了第1件衣服
20 生产了第2件衣服
21 生产了第3件衣服
22 生产了第4件衣服
23 生产了第5件衣服
24 生产了第6件衣服
25 生产了第7件衣服

二、举例:

公司要做市场推广,需要印刷2,000,000本宣传册,单价15元。可以一次性拿出3000万让印刷厂全部印出来,搬回来找个地方放着,每次推广活动的时候拿一点发出去。

1 def book():
2     l = []
3     for i in range(1,2000000):
4         l.append('第%s本宣传册'%i)
5     return l
6 
7 book = book()
8 print(book)

这样显然不合理,从经济上讲,不可能一次性拿出3000万去印刷宣传册。从程序设计上讲,一次性生成一个2000000个元素的列表,会占用很大的内存,运行效率低。

如果能跟印刷厂签个合同,比如说2年内购买2000000册,2年内有推广活动时就让印刷厂印出一少部分,这样就完美了。

 1 def book():
 2     for i in range(1,2000000):
 3         yield '第%s本宣传册'%i
 4 
 5 g = book()
 6 print(next(g))
 7 print(next(g))
 8 print(g.__next__())
 9 print(g.__next__())
10 print(next(g))
11 
12 for i in range(50):
13     print(next(g))
14 
15 for i in range(100):
16     print(g.__next__())

三、send

1、send和next的效果一样,都能获取到yield返回的值。

2、send在获取下一个yield的返回值的同时,给上一个yield的位置传递一个数据。

3、第一次使用生成器的时候,必须使用next回去yield的返回值。

4、最后一个yield不能接收外部传递的值。

def generator():
    print(123)
    ret1 = yield 'aaa'
    print(ret1)
    print(456)
    ret2 = yield  'bbb'
    print(ret2)

g = generator()
print(g.__next__())
print(g.send('我是send传过来的值'))  # send将值传给了第一个yield
# print(g.__next__())   # 这个地方会报错StopIteration,因为前面有一个__next__和一个send了,generator生成器里面只有两个yield

四、举例

1、获取移动平均值

def average():
    sum = 0
    count = 0
    avg = 0
    while True:
        num = yield avg
        sum += num
        count += 1
        avg = sum / count


g = average()
g.__next__()
ret = g.send(10)
print(ret)
ret = g.send(20)
print(ret)

执行过程:

优化后的生成器,使用装饰器与激活生成器,在调用的时候直接使用send,不用在send之前调用__next__激活生成器了。

def init(func):
    def inner(*args,**kwargs):
        g = func(*args,**kwargs)
        g.__next__()
        return g
    return inner

@init
def average():
    sum = 0
    count = 0
    avg = 0
    while True:
        num = yield avg
        sum += num
        count += 1
        avg = sum / count

g = average()
ret = g.send(10)
print(ret)
ret = g.send(20)
print(ret)

执行过程:

五、yield from

def gen():
    s = 'abcd'
    for i in s:
        yield i

    for j in range(4):
        yield j

g = gen()
for i in g:
    print(i)
def gen():
    s = 'abcd'
    yield from s
    yield  from range(4)

g = gen()
for i in g:
    print(i)

六、列表推导式

# 列表循环
l = []
for i in range(5):
    l.append('鸡蛋%s'%i)
print(l)   # ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4']

# 列表推导式
print(['鸡蛋%s'%i for i in range(5)])  # ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4']
# 列表循环
s = 0
for i in range(5):
   s += i**2
print(s)

# 列表推导式
print(sum([i**2 for i in range(5)]))

七、生成器表达式

和列表推导式相比,只要把列表推导式中的[]换成()就变成一个生成器表达式。

print(x for x in range(5))       # <generator object <genexpr> at 0x009943B0>
print(sum(x for x in range(5)))  # 10

#
注意:这两个东西的返回值,第一个输出的是生成器,第二个输出的是sum的结果

八、各种推导式

1、列表推导式

print([i for i in range(31) if i%3 == 0])
print([i*i for i in range(31) if i%3 == 0])

2、字典推导式

# key和value互换
dic = {'a':10, 'b':20}
print({dic[item]:item for item in dic})  # {10: 'a', 20: 'b'}

# 将key相同的value值合并(忽略key的大小写),并将key都改成小写
dic = {'a':10, 'b':20, 'A':30, 'B':40}
ret = {k.lower():dic.get(k.lower(),0) + dic.get(k.upper(),0) for k in dic}
print(ret)  # {'a': 40, 'b': 60}

3、集合推导式

print({i**2 for i in [1,-1,2,-2,4]})  # {16, 1, 4}

九、面试题

面试题一:

def demo():
    for i in range(4):
        yield i

g=demo()

g1=(i for i in g)  # 这一句在list(g1)在执行时才会被调用
g2=(i for i in g1) # 这一句在list(g2)在执行时才会被调用

print(list(g1))  # [0, 1, 2, 3]
print(list(g2))  # []

结果解析:
为什么是这个运行结果?
生成器有一个特点,那就是如果生成器里面的值被取完了,再取就取不到了。
在上面的题目中,g1=(i for i in g)和g2=(i for i in g1)在开始的时候并没有执行,只有在执行到list(g1)和list(g2)的时候才会执行。因为生成器表达式和生成器函数一样只返回一个生成器,并不会执行里面的代码。
在执行list(g1)的时候,会强制将生成器g1的值转换成list,g1又会从生成器g中取值。所以list(g1)执行后,生成器g1里面的值已经被全部取出来了。
在执行list(g2)的时候,会强制将生成器g2的得转换成list,g2这时会从g1去取值,因为g1里面的值已经在前面被取完了,所以list(g2)就取不到任何值了。
如果将print(list(g1))这一段代码注释掉,print(list(g2))就会能取到值,返回[0, 1, 2, 3]

面试题二:

def add(n,i):
    return n+i

def test():
    for i in range(4):
        yield i

g=test()
for n in [1,10]:
    g=(add(n,i) for i in g)

ret = list(g)
print(ret)    # [20, 21, 22, 23]

# 上面的程序似乎看不明白,可以把上面的for循环拆开来看就比较容易了,如下:
def add(n,i):
    return n+i

def test():
    for i in range(4):
        yield i

g=test()

n = 1
g=(add(n,i) for i in g)
n = 10
g=(add(n,i) for i in g)

# 到这里为止,上面的代码都不会执行,在list(g)被执行的时候才会去从生成器g中取值
ret = list(g)  
print(ret)    # [20, 21, 22, 23]

思考下面的题目,为什么输出的是:[15, 16, 17, 18]

提示,将for循环也拆开来看

def add(n,i):
    return n+i

def test():
    for i in range(4):
        yield i

g=test()
for n in [1,10,5]:
    g=(add(n,i) for i in g)

ret = list(g)
print(ret)    # [15, 16, 17, 18]
原文地址:https://www.cnblogs.com/Ryan-Fei/p/12157657.html