zoukankan      html  css  js  c++  java
  • python学习笔记:第12天 列表推导式和生成器

    1. 迭代器

    什么是生成器呢,其实生成器的本质就是迭代器;在python中有3中方式来获取生成器(这里主要介绍前面2种)

    • 通过生成器函数获取
    • 通过各种推导式来实现生成器

    生成器函数

    我们来看一个普通的函数:

    In[2]: def func1():
      ...:     print('aaaa')
      ...:     return 1111
      ...: 
    In[3]: fun = func1()
    aaaa
    In[4]: print(fun)
    1111
    

    那么生成器函数跟普通函数有什么不同呢,我们只要把其中的return换成yield关键字参数就是生成器函数了:

    In[5]: def func1():
      ...:     print('aaaa')
      ...:     yield 1111
      ...: 
    In[6]: fun = func1()          # 此时并没有任何打印信息,可以说明函数并没有执行
    In[7]: print(fun)             # 从输出可以看出这是一个生成器对象
    <generator object func1 at 0x0000016F900D6DB0>
    

    从上面的结果来看,我们发现函数func1根本就没有执行,而最后打印的是一个内存地址,这个就是生成器很明显的一个特性:惰性计算,那么我们要怎么执行它呢?我们可以回顾一下迭代器的取值方法:使用迭代器的__next__的方法可以取到迭代器的一个值,那生成器的本质就是迭代器,那我们也可以试下可以这样取值

    In[8]: fun.__next__() # 从输出可以看出,yield也和return一样可以有返回值
    aaaa                  # 这里我们就可以看到函数中的aaaa也打印了,表示函数在此处才执行
    Out[8]: 1111
    

    我们再来看个例子,观察下生成器是怎么工作的:

    In[9]: def func1():
      ...:     print('aaaa')
      ...:     yield '我是第一个yield'
      ...:     print('bbbb')
      ...:     yield '我是第二个yield'
      ...:     print('cccc')
      ...:     
    In[10]: gen = func1()           # 这里得到的是一个生成器,此处并不会运行函数
       ...: print(gen)
    <generator object func1 at 0x0000016F900F8BA0>
    In[11]: print(gen.__next__())   # 首次执行生成器的__netx__()函数时,开始执行函数,
    aaaa                            # 直到遇到yield时返回,并且yield也可以有返回值
    我是第一个yield
    In[12]: print(gen.__next__())   # 再次运行__netx__()函数时,会继续执行函数(从上次yield的位置继续执行)
    bbbb
    我是第二个yield
    In[13]: print(gen.__next__())   # 再次执行__next__()方法继续执行,此处再往下执行时没有了yield关键字,
    cccc                            # 会抛出StopIteration异常(但时会执行后面的代码)
    Traceback (most recent call last):
      File "D:Environmentpython-virtualenvjupyterlibsite-packagesIPythoncoreinteractiveshell.py", line 3265, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-13-9340d28f24b7>", line 1, in <module>
        print(gen.__next__())
    StopIteration
    

    从上面我们呢可以总结出:

    • yield也可以像return一样也是返回值
    • yield执行完之后会返回到调用者,执行后续的代码,直到再次调用__next__方法,此时生成器函数再从上次停止的位置继续执行
    • 当执行__next__方法后没有yield关键字时,会抛出StopIteration异常,但是会执行yield后面的代码

    send方法

    接下来我们来看send⽅法, send和__next__()⼀样都可以让⽣成器执⾏到下⼀个yield

    In[14]: def eat():
       ...:     print("aaaa")
       ...:     a = yield 1111
       ...:     print("a=",a)
       ...:     b = yield "bbbb"
       ...:     print("b=",b)
       ...:     c = yield "cccc"
       ...:     print("c=",c)
       ...:     yield "GAME OVER"
       ...:     
    In[15]: gen = eat() # 获取⽣成器
    In[16]: ret1 = gen.__next__()
       ...: print(ret1)
    aaaa
    1111
    In[17]: ret2 = gen.send("我send了一个参数给a")
       ...: print(ret2)
    a= 我send了一个参数给a              # 可以看出send的数据是被上一个yield前的a给接收了
    bbbb
    In[18]: ret3 = gen.send("我send了一个参数给b")
       ...: print(ret3)               # 这里send的数据也是被b接收了
    b= 我send了一个参数给b
    cccc
    In[19]: ret4 = gen.send("我send了一个参数给c")
       ...: print(ret4)
    c= 我send了一个参数给c
    GAME OVER
    
    

    send和__next__():

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

    2. 推导式

    列表推导式

    关于列表推导式,其实之前的文章中已经使用过,这里再正式介绍下;假设我们要打印1到20之间的奇数,照之前正常的写法我们要这么写:

    # 假设有一个需求,要写一个循环遍历1到20之间所有的奇数
    lst = []
    for i in range(1, 21):
        if i % 2 == 1:
            lst.append(i)
    print(lst)
    # 结果:
    # [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] 
    

    列表推导式的语法为:

    • 第一种只使用for循环遍历
    [expr for item in itratorable]
    
    # 相当于以下代码
    ret = []
    for item in iterable:
      ret.append(expr)
    
    • 第二种for循环遍历再加if条件判断
    [expr for item in iterable if cond]
    
    # 相当于以下结构代码
    ret = []
    for item in iterable:
        if cond:
            ret.append(expr)
    

    第三种for循环加if双分支结构,注意此时的if/else语句要写在for语句前面

    [expr1 if cond else expr2 for item in iterable ]
    
    # 相当于以下代码
    ret = []
    for item in iterable:
        if cond:
            ret.append(expr1)
        else:
            ret.append(expr2)
    

    对于上面的例子使用列表推导式可以这样写:

    # 使用推导式:
    lst = [i for i in range(1, 21) if i % 2 == 1]
    print(lst)
    # 结果:
    # [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
    

    使用列表推导式我们可以发现代码时精简了许多,而且代码的可读性更高了,其实还有一个优势是推导式速度更快:

    In [1]: %%timeit
       ...: lst1 = []
       ...: for i in range(10000):
       ...:     lst1.append(i)
       ...:
    788 µs ± 14.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    
    In [2]: %%timeit
       ...: lst1 = [i for i in range(10000)]
       ...:
    307 µs ± 1.84 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    
    In [3]:
    

    从上面的结果分析,使用列表推导式生成列表的方式要比普通for循环的效率要高很多

    字典推导式

    字典跟列表推导式的语法非常相似,使用{}括起来,然后在里面想列表推导式一样写自己的表达式即可:

    dic = {expr for k, v in iterable if cond}        # 这里的expr表达式可以写成:k: v的形式
    
    # 相当于以下代码
    dic = dict()
    for k, v in iterable:
        if cond:
            expr(dic)
    

    例如,把字典中的键值对都调换以下可以用如下方法:

    dic = {"张无忌":"赵敏", "杨过":"小龙女", "郭靖":"黄蓉"}
    # dic = {'k1':'v1', 'k2': 'v2', 'k3': 'v3'}
    
    dic = {v: k for k, v in dic.items()}
    print(dic)
    

    生成器表达式

    对于生成器表达式来说,只需要把列表推导式的中括号换成小括号就可以了:

    In[20]: def inc(x):
       ...:     print('inc {0}'.format(x))
       ...:     return x+1
       ...: 
    In[21]: g = (inc(x) for x in range(10))             # 这里的g就是一个生成器对象
    In[22]: print(g)
    <generator object <genexpr> at 0x0000016F90161DB0>
    In[23]: print(g.__next__())
    inc 0
    1
    In[24]: print(g.__next__())                         # 也可以使用__next__方法取出一个值
    inc 1
    2
    In[25]: print(g.__next__())
    inc 2
    3
    In[26]: next(g)                                     # 使用netx()和__next__()方法是一样的
    inc 3
    Out[26]: 4
    In[27]: next(g)
    inc 4
    Out[27]: 5
    
    

    当然,生成器表达式也可以跟其他推导式一样套用if语句,其语法都是一样的,这里就不做介绍了。

    ⽣成器表达式和列表推导式的区别:

    • 列表推导式比较耗内存. ⼀次性加载. ⽣成器表达式⼏乎不占⽤内存. 使⽤的时候才分
      配和使⽤内存

    • 得到的值不⼀样. 列表推导式得到的是⼀个列表. ⽣成器表达式获取的是⼀个⽣成器.

    ⽣成器的惰性机制: ⽣成器只有在访问的时候才取值. 说⽩了. 你找他要他才给你值. 不找他
    要. 他是不会执⾏的.

    def func():
      print(111)
      yield 222
    
    g = func()            # ⽣成器g
    g1 = (i for i in g)   # ⽣成器g1. 但是g1的数据来源于g
    g2 = (i for i in g1)  # ⽣成器g2. 来源g1
    print(list(g))        # 获取g中的数据. 这时func()才会被执⾏. 打印111.获取到222. g完毕.
    print(list(g1))       # 获取g1中的数据. g1的数据来源是g. 但是g已经取完了. g1 也就没有数据了
    print(list(g2))       # 和g1同理
                          # 注:list中有for的调用,可以迭代遍历生成器元素
    #结果:
    # 1111
    # [222]
    # []
    # []
    

    访问生成器的另一种方法

    使用yield from iterator语句

    In[28]: def test():
       ...:     l1 = [1, 2, 3, 4]
       ...:     l2 = ['a', 'b', 'c', 'd']
       ...:     yield from l1             # 
       ...:     yield from l2
       ...:     
    In[29]: g = test()
    In[30]: for i in g:
       ...:     print(i)
       ...:     
    1
    2
    3
    4
    a
    b
    c
    d
    
    
  • 相关阅读:
    状态模式
    简单密码再次加密
    服务层定义自己的服务异常类
    必备网络基础知识(持续补充)
    MongoDB基础入门
    Git命令整理
    算法基础(四)
    RabbitMQ消息队列
    设计模式(23种)
    二叉树知识点
  • 原文地址:https://www.cnblogs.com/zpzhue1/p/9892600.html
Copyright © 2011-2022 走看看