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
    
    
  • 相关阅读:
    leetcode Remove Linked List Elements
    leetcode Word Pattern
    leetcode Isomorphic Strings
    leetcode Valid Parentheses
    leetcode Remove Nth Node From End of List
    leetcode Contains Duplicate II
    leetcode Rectangle Area
    leetcode Length of Last Word
    leetcode Valid Sudoku
    leetcode Reverse Bits
  • 原文地址:https://www.cnblogs.com/zpzhue1/p/9892600.html
Copyright © 2011-2022 走看看