• 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]
  • 相关阅读:
    年度回忆录(2012.102013.01)
    Java中的Annotation(1)三个基本Annotation
    Java7中的文件和目录管理Path类
    Struts1和Struts2核心控制器的执行原理
    java中的IO基础3
    动态代理(2)动态代理和AOP
    java中的IO基础
    《嫌疑犯x的献身》看完了。。。
    像NHibernate致敬ado.net entity framework的范型DAO和open session in view实现
    我的MBTI职业性格测试
  • 原文地址:https://www.cnblogs.com/Ryan-Fei/p/12157657.html
Copyright © 2020-2023  润新知