zoukankan      html  css  js  c++  java
  • Python学习-生成器、列表推导式、生成器表达式、字典推导式、集合推导式

    记录下python中生成器、列表推导式、生成器表达式、字典推导式、集合推导式的内容。

    生成器

    生成器本质上就是迭代器,是自己用python代码构建出的一种数据结构,获取生成器有三种方式:

    1. 使用生成器函数
    2. 使用生成器表达式
    3. python内部提供

    下面使用生成器函数,来创建生成器,关键字为yield。

    # 生成器函数
    def func():
        print('我不是函数,我是生成器')
        yield 520
        print('是否执行')
        yield 1314
    
    ret=func() # 此时func不执行
    print(ret) # <generator object func at 0x105bffcf0> 说明是生长器
    
    print(next(ret))
    print(next(ret))
    
    # 执行结果
    我不是函数,我是生成器
    520
    是否执行
    1314
    

    可以看出520和1314均是yield返回的结果,它与return类型也能返回数据,两者有什么区别呢。

    • return:会终止函数,如果有多个return只有一个生效,并且return会返回值给函数的执行。
    • yield:只要一个函数中有yield,那么它就是生成器函数,并且一个next对应一个yield,执行到函数体哪里也是可以用光标的位置去理解。

    惰性机制

    生成器到底有什么优势呢,看一个例子就明白,它利用了惰性机制,不要值就坚决不取值。

    如果去菜市场买鸡蛋,想要200个鸡蛋,不使用生成器使用列表接收,就一次性得到200个鸡蛋。如果使用生成器,就可以分批次来取,不需要一次拿200个鸡蛋,这就是使用生成器的不同,可以节省内存空间,不需要一次性放到内存里,取多少拿多少。

    # 没有使用生成器
    def get_eggs():
        li=[]
        for i in range(1,201,1):
            li.append(f'鸡蛋{i}号')
        print(li)
    # 一次性得到200个鸡蛋
    get_eggs()
    
    
    # 使用生成器函数
    def get_eggs_2():
        for i in range(1,201,1):
            yield f'鸡蛋{i}号'
    
    ret=get_eggs_2()
    
    print('----第一次消费了100个鸡蛋----')
    for r in range(1,101,1):
        # 消费了1-100的鸡蛋
        print(next(ret))
    
    # 再执行一次
    print('----第二次消费了100个鸡蛋----')
    for r in range(1,101,1):
        # 消费了101-200的鸡蛋
        print(next(ret))
    

    yield与yield from

    yield from一个可迭代对象,其实就跟写了多个yield没区别,看下面例子。

    # yield
    def func():
        li=[1,2,3,4,5]
        yield li
    ret=func()
    print(next(ret)) # [1,2,3,4,5]
    
    # yield from
    def func():
        li=[1,2,3,4,5]
        # 类似写了5个yield
        yield from li
    ret=func()
    print(next(ret)) 
    print(next(ret))
    print(next(ret))
    print(next(ret))
    print(next(ret))
    
    # 执行结果
    1
    2
    3
    4
    5
    

    可以在函数中定义多个yield from,使用for循环可以直接从生成器取值。

    def func():
        l1=['messi','herry','ronald']
        l2=['梅西','亨利','罗纳尔多']
        yield from l1
        yield from l2
    
    ret=func()
    # 生成器可以使用for循环获取内部的元素
    for i in ret:
        print(i,type(i))
        
    # 执行结果
    messi <class 'str'>
    herry <class 'str'>
    ronald <class 'str'>
    梅西 <class 'str'>
    亨利 <class 'str'>
    罗纳尔多 <class 'str'>
    

    next与send

    send()和next()都是让生成器向下走一个yield位置,但send可以给上⼀个yield的位置传递值, 不能给最后一个yield发送值,在第一次执⾏生成器代码的时候不能使⽤send。

    def eat():
        print("我吃什什么啊")
        a = yield "馒头"
        print("a=",a)
        b = yield "⼤大饼"
        print("b=",b)
        c = yield "⾲韭菜盒⼦子"
        print("c=",c)
        yield "GAME OVER"
    gen = eat() # 获取生成器
    ret1 = gen.__next__()
    print(ret1)
    ret2 = gen.send("胡辣汤") # send到a的位置
    print(ret2)
    ret3 = gen.send("狗粮") # send到b的位置
    print(ret3)
    ret4 = gen.send("猫粮") # send到c的位置
    print(ret4)
    
    # 执行结果
    我吃什什么啊
    馒头
    a= 胡辣汤
    ⼤大饼
    b= 狗粮
    ⾲韭菜盒⼦子
    c= 猫粮
    GAME OVER
    

    列表推导式

    列表推导式为用一行代码,构建一个比较复杂并且有规律的列表,列表推导式分为两种:

    • 循环模式:[加工后变量 for 变量 in iterable]
    • 筛选模式:[加工后变量 for 变量 in iterable if 条件]

    首先感受一下列表推导式与普通方式构建一个有规律列表的区别。下面列表推导式使用的是循环模式。

    # 使用普通方式构建列表[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    li = []
    for i in range(1, 11, 1):
        li.append(i)
    print(li)
    
    # 使用列表推导式 --是循环模式
    li = [i for i in range(1, 11)]
    print(li)
    
    # 执行结果均一样
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    

    再看几个循环模式的例子。

    # 1 将10以内的所有整数的平方写入到列表
    li = [i ** 2 for i in range(1, 11)]
    print(li)
    
    # 2 100以内所有的偶数写入到列表
    li = [i for i in range(2, 102, 2)]
    print(li)
    
    # 3 将python1期到python200期写入列表
    li = [f'python{i}期' for i in range(1, 201)]
    print(li)
    

    当在列表推导式末尾加上判断条件,就变成筛选模式。

    # 1 30以内能被3整除的数
    li = [i for i in range(1, 31) if i % 3 == 0]
    print(li)
    
    # 2 过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母
    li = ['messi', 'ronald', 'herry', 'a', 'b', 'c']
    li_2 = [s.upper() for s in li if len(s) > 2]
    print(li_2)
    
    # 3 找到嵌套列表中名字含有两个'e'的所有名字,并全大写
    names = [['tom', 'jerry', 'jefferson', 'andrew', 'steven'], ['alice', 'ana', 'wendy', 'jennifer', 'sherry']]
    # 正常写法
    li = []
    for name in names:
        for s in name:
            # 使用count方法判断
            if s.count('e') == 2:
                li.append(s.upper())
    
    print(li)
    
    # 使用列表推导式,比较牛逼的写法
    li=[name.upper() for l in names for name in l if name.count('e')==2]
    print(li)
    
    # 执行结果
    [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
    ['MESSI', 'RONALD', 'HERRY']
    ['JEFFERSON', 'STEVEN', 'JENNIFER']
    ['JEFFERSON', 'STEVEN', 'JENNIFER']
    

    列表推导式给人的感觉就是简洁高逼格,人类高质量代码的味道,但是一旦出现问题也不好查,其主要特点如下。

    • 有毒,会让人沉迷,比较适合构建有规律的的列表
    • 需要三层循环才能构建的列表或其他可迭代对象,不建议使用
    • 不方便debug查找问题

    感受一下高逼格的写法。

    # 构建一个列表[2,3,4,5,6,7,8,9,'J','Q','K','A']
    # 创建列表的两种方式,列表推导式和list创建
    li=[s for s in range(2,10)]+list('JQKA')
    print(li)
    

    生成器表达式

    生成器表达式与列表推导式几乎一样,写时需要加一个小括号,同样有循环模式和筛选模式。下面打印li的类型为generator,说明为生成器。

    # 生成器表达式
    li=(i for i in range(1,11))
    print(li,type(li))
    print(next(li))
    print(next(li))
    print(next(li))
    print(next(li))
    
    # 执行结果
    <generator object <genexpr> at 0x107c9ed68> <class 'generator'>
    1
    2
    3
    4
    

    列表推导式和生成器表达式有啥区别呢,主要如下:

    1 写法有区别,列表推导式使用[],生成器表达式使用()

    2 生成器表达式本质是iterator,列表推导式本质上是iterable

    li=[i for i in range(1,11)]
    print('__iter__' in dir(li)) # True,说明列表推导式本质上是iterable
    li=(i for i in range(1,11))
    print('__iter__' in dir(li) and '__next__' in dir(li)) # True,说明生成器表达式本质是iterator
    

    字典推导式

    类似列表推导式,了解即可。

    # 将l1的元素作为key,l2的元素作为value组合成字典
    l1=['name','age','salary']
    l2=['clyang',22,9000]
    li={l1[i]:l2[i] for i in range(len(l1))}
    print(li)
    
    # 将下面字典key-value调换,变成新字典
    dic={'name':'clyang','age':33}
    new_dic={dic[key]:key for key in dic}
    print(new_dic)
    
    # 执行结果
    {'name': 'clyang', 'age': 22, 'salary': 9000}
    {'clyang': 'name', 33: 'age'}
    

    集合推导式

    类似列表推导式,只是集合没有重复元素,了解即可。

    l1=[1,2,3,3,-4,-5,-6]
    s={abs(number) for number in l1}
    print(s)
    
    # 执行结果
    {1, 2, 3, 4, 5, 6}
    

    相关练习

    (1)看代码写结果,这个题巨坑,第一次就搞错了。

    def add(a, b):
        return a + b
    def test():
        for r_i in range(4):
            yield r_i
    g = test()
    for n in [2, 10]:
        g = (add(n, i) for i in g) # 咋一看就是(12,13,14,15)
    print(list(g)) # 实际是[20, 21, 22, 23]
    

    其实上面for循环控制次数2次,执行两次后g=(add(n,i) for i in (add(n,i) for i in g)),执行到最后n等于10,所以g=(add(10,i) for i in (add(10,i) for i in g))。

    (2)列表推导式使用练习

    # 1 求出50以内能被3整除的数的平方,并放入到列表中
    li=[i**2 for i in range(1,50,1) if i%3==0]
    print(li)
    
    # 2 构建一个列表:[(0,1),(1,2),(2,3),(3,4),(4,5),(5,6)]
    li=[(i,i+1) for i in range(6)]
    print(li)
    
    # 3 有一个列表l1=['alex','wusir','老男孩','太白']
    # 将其构造成这种列表['alex0','wusir1','老男孩2','太白3']
    l1=['alex','wusir','老男孩','太白']
    li=[l1[index]+str(index) for index in range(4)]
    print(li)
    
    # 4 现有如下数据
    x={
        'name':'alex',
        'value':[{'timestamp':190111,'value':100},
                {'timestamp':200222,'value':200},
                {'timestamp':210333,'value':300},
                {'timestamp':220444,'value':400},
                {'timestamp':230555,'value':500},
        ]
    }
    
    # 将上面的数据通过列表推导式转换成下面的类型
    # [[190111,100],[200222,200],[210333,300],[220444,400],[230555,500]]
    li=[[dic['timestamp'],dic['value']] for dic in x.get('value')]
    print(li)
    
    # 执行结果
    [9, 36, 81, 144, 225, 324, 441, 576, 729, 900, 1089, 1296, 1521, 1764, 2025, 2304]
    [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
    ['alex0', 'wusir1', '老男孩2', '太白3']
    [[190111, 100], [200222, 200], [210333, 300], [220444, 400], [230555, 500]]
    

    (3)使用2层循环列表推导式,构建笛卡尔积。

    # 有一个列表,里面包含三种不同尺寸的T恤,每种尺寸都有两种颜色,使用列表推导式构建尺寸和颜色的笛卡尔积
    colors=['black','white']
    sizes=['S','M','L']
    li=[(color,size) for color in colors for size in sizes]
    print(li)
    
    # 6 构建一个列表,打印扑克牌中除大小王,所有的牌类
    shapes=['♥','♠','♣','♦']
    numbers=['A','2','3','4','5','6','7','8','9','10','J','Q','K']
    li=[(shape,number) for shape in shapes for number in numbers]
    print(li)
    
    # 执行结果
    [('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]
    [('♥', 'A'), ('♥', '2'), ('♥', '3'), ('♥', '4'), ('♥', '5'), ('♥', '6'), ('♥', '7'), ('♥', '8'), ('♥', '9'), ('♥', '10'), ('♥', 'J'), ('♥', 'Q'), ('♥', 'K'), ('♠', 'A'), ('♠', '2'), ('♠', '3'), ('♠', '4'), ('♠', '5'), ('♠', '6'), ('♠', '7'), ('♠', '8'), ('♠', '9'), ('♠', '10'), ('♠', 'J'), ('♠', 'Q'), ('♠', 'K'), ('♣', 'A'), ('♣', '2'), ('♣', '3'), ('♣', '4'), ('♣', '5'), ('♣', '6'), ('♣', '7'), ('♣', '8'), ('♣', '9'), ('♣', '10'), ('♣', 'J'), ('♣', 'Q'), ('♣', 'K'), ('♦', 'A'), ('♦', '2'), ('♦', '3'), ('♦', '4'), ('♦', '5'), ('♦', '6'), ('♦', '7'), ('♦', '8'), ('♦', '9'), ('♦', '10'), ('♦', 'J'), ('♦', 'Q'), ('♦', 'K')]
    

    (4)看下面代码,能否对其进行简化。

    def chain(*args):
        for it in args:
            # for i in it:
            #     yield i
            # 上面使用yield from来简化,优化内存循环,提高了效率
            yield from it
    # 调用
    g=chain('messi',[1,2,3])
    # next调用
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    # 或者将生成器转换成列表,然后再循环打印
    #li=list(g)
    #for item in li:
    #    print(item)
    
    # 执行结果
    m
    e
    s
    s
    i
    1
    2
    3
    

    yield from可以直接作用可迭代对象(这里为元祖),相当于多个yield,优化内存循环,提高了效率。

    (5)看代码写结果

    v=[i%2 for i in range(10)]
    print(v) # [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
    k=(i%2 for i in range(10))
    print(k) # 
    
    # 看代码求结果(面试题)
    def demo():
        for i in range(4):
            yield i
    
    g=demo()
    # 以下都是生成器表达式
    g1=(i for i in g) # 注意g1是生成器
    g2=(i for i in g1) # g2来自g1
    
    print(list(g1)) # 在list方法里已经对g1取值完了
    print(g1) # 
    print(list(g2)) # g1已经取完,g2为[]
    
    # 执行结果
    0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
    <generator object <genexpr> at 0x10b6bfd68>
    [0, 1, 2, 3]
    <generator object <genexpr> at 0x10b6bfcf0>
    []
    

    PS:以上,理解不一定正确,学习就是一个不断认识和纠错的过程,如果有误还请批评指正。

    写于9.12凌晨5点:我留下她的东西,现在不管是否有留恋的意思,都改变不了对你的伤害,真心对不起。自己情绪很不稳定内心也扭曲,希望以后不要再伤害到你,不管以后我们走向何方,都希望你能永远幸福、快乐、青春。

    参考博文:

    (1)《老男孩python》

  • 相关阅读:
    [每天解决一问题系列
    [每天解决一问题系列
    [每天解决一问题系列
    nodejs&mongo&angularjs
    [转]Express框架
    [转]Use HandleBars in Express
    10 Tips for Optimizing Your Website’s Speed
    One difference between AngularJS' $location and window.location
    Why does Http header contains "X-SourceFiles"?
    JavaScript数组常用方法
  • 原文地址:https://www.cnblogs.com/youngchaolin/p/15257302.html
Copyright © 2011-2022 走看看