zoukankan      html  css  js  c++  java
  • 列表生成式、生成器&迭代器

    一、列表生成式(List Comprehensions)

    1、案例——列表每个值加1

      先有列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求你把列表里的每个值加1,怎么实现?

    方法一:

    a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    b = []
    for i in a:b.append(i+1)
    a = b
    print(a)

      此方法内存中会同时有两份列表,因此不适合处理大规模数据。

    方法二:

    a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    for index,i in enumerate(a):
        a[index] +=1
    print(a)

    方法三:

    a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    a = map(lambda x:x+1, a)   # <map object at 0x1018da5f8>
    print(a)

      利用map方法根据提供的函数(lambda)对指定序列做映射。

    方法四:

    # 列表生成式:一行代码完成列表操作
    a = [i+1 for i in range(10)] 

    2、列表生成式概念和用法

      列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。

    # 写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来
    >>> [x * x for x in range(1, 11)]
    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    
    # for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方
    >>> [x * x for x in range(1, 11) if x % 2 == 0]
    [4, 16, 36, 64, 100]
    
    # 用三元运算生成列表
    >>> a = range(1,11)
    >>> a = [i if i < 5 else i*i for i in a]
    >>> a
    [1, 2, 3, 4, 25, 36, 49, 64, 81, 100]
    
    # 使用两层循环,可以生成全排列
    >>> [m + n for m in 'ABC' for n in 'XYZ']
    ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
    
    # 列表生成式也可以使用两个变量来生成list
    >>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
    >>> [k + '=' + v for k, v in d.items()]
    ['x=A', 'y=B', 'z=C']
    
    # 字符串操作,都变为小写
    >>> L = ['Hello', 'World', 'IBM', 'Apple']
    >>> [s.lower() for s in L]
    ['hello', 'world', 'ibm', 'apple']

       由此可以看到,通过列表生成式可以直接创建一个列表。列表创建在内存中,因此列表容量受到内存限制。特别是对一个元素量很大的列表,仅需访问前几个元素时,尤其浪费空间。

    二、生成器(generator)

    1、生成器定义

       列表元素可以按照某种算法推算出来(有规律的数组),则可以在循环的过程中不断推算出后续的元素。这种方式就不必创建完整的list,可以节省大量的空间

      python中,这种一边循环一边计算的机制,称为生成器:generator

    2、生成器创建方法一:将列表生成式的‘[]’改为‘()’

      创建列表生成式和生成器的区别,仅仅是最外层的[]和()。

      如果要打印出生成器的元素,可以通过nex()函数获取generator的下一个返回值。

    # 生成器保存的是公式,取一次创建一次,只能往前不能后退
    >>> a2 = (i for i in range(1000))
    >>> a2
    <generator object <genexpr> at 0x103761a98>
    >>> next(a2)
    0
    >>> next(a2)
    1
    
    #生成器走完时,会报错:StopIteration
    >>> a3 = (i for i in range(5))   # 限制5个
    >>> a3
    <generator object <genexpr> at 0x103761e08>
    >>> next(a3)
    0
    >>> next(a3)
    1
    >>> next(a3)
    2
    >>> next(a3)
    3
    >>> next(a3)
    4
    >>> next(a3)
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    StopIteration

      生成器保存的是算法,每次调用next(g)就计算出g的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误。

      创建生成器后,很少会调用next(),一般是通过for循环来迭代

    a3 = (i for i in range(5))
    for i in a3:   # 使用for循环来迭代生成器,不会出现StopIteration报错,直接结束。
        print(i)
    '''
    0
    1
    2
    3
    4
    '''
    
    a2 = (i for i in range(3))
    while True:    # while循环不适用
        next(a2)   # 报StopIteration错误

    3、生成器创建方法二:一个函数定义中包含yield关键字,函数为生成器(generator)

      例如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数均可由前两个数相加得到:1, 1, 2, 3, 5, 8, 13, 21, 34,....

      列表生成式写不出斐波拉契数列,但是函数却可以轻松打印:

    # 用函数来写生成器,以斐波那契数列为例
    # 重点是赋值语句:a,b = b,a+b
    def fib(max):  
        n, a, b = 0, 0, 1
        while n < max:
            print(b)   # 每次打印b
            a, b = b, a + b   # 0,1——>1,1——>1,2——>2,3
            n = n + 1   # n用来计数,每次自加1
        return 'done'
    
    fib(10)

      赋值语句:a, b = b , a + b   

      相当于:t = (b, a + b)   a = t[0]   b = t[1]   t是一个tuple

      fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

    >>> def fib_g(max):
    ...     n, a, b = 0, 0, 1
    ...     while n < max:
    ...         print('before yield')
    ...         yield b   # yield 把函数的执行过程冻结在这一步,并且把b的值返回给外面的next()
    ...         print(b)
    ...         a, b = b, a+b
    ...         n = n + 1
    ...     return 'done'
    ...
    >>> f = fib_g(15)  # 将函数转换为生成器,有了yeild后,函数名(参数)根本不执行
    >>> next(f)
    before yield
    1
    >>> next(f)
    1
    before yield
    1
    >>> next(f)
    1
    before yield
    2
    >>> next(f)
    2
    before yield
    3
    >>> next(f)
    3
    before yield
    5

      这种生成器和函数相似,但与函数的执行流程不同:

      函数是顺序执行,遇到return语句或最后一行函数语句就返回。

      函数转化为生成器后,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

    def fib_g(max):
        n, a, b = 0, 0, 1
        while n < max:
            # print('before yield')
            yield b   # yield 把函数的执行过程冻结在这一步,并且把b的值返回给外面的next()
            # print(b)
            a, b = b, a+b
            n = n + 1
        return 'done'
    # 生成器使用意义:可以将函数执行中的状态、数据返回到外部来
    
    data = fib_g(10)
    print(data)
    
    print(data.__next__())
    print(data.__next__())
    print("干点别的事")
    print(data.__next__())
    print(data.__next__())
    print(data.__next__())
    print(data.__next__())
    
    """
    <generator object fib_g at 0x1014670f8>
    1
    1
    干点别的事
    2
    3
    5
    8
    
    """

      在上例中,循环过程中不断调用yield,就会不断中断。而且需要设置循环退出条件,否则会产生无限数列。

      将函数改写为生成器后,通常不会用next()来获取下一个值,而是使用for循环来迭代。

    def fib_g(max):
        n, a, b = 0, 0, 1
        while n < max:
            yield b   # yield 把函数的执行过程冻结在这一步,并且把b的值返回给外面的next()
            a, b = b, a+b
            n = n + 1
        return 'done'
    
    for n in fib_g(6):
        print(n)
    
    """
    1
    1
    2
    3
    5
    8
    """

      for循环调用生成器,拿不到生成器的return语句的返回值,要拿到返回值,必须捕获StopIteration错误。

    def fib_g(max):
        n, a, b = 0, 0, 1
        while n < max:
            yield b   # yield 把函数的执行过程冻结在这一步,并且把b的值返回给外面的next()
            a, b = b, a+b
            n = n + 1
        return 'done'
    
    g = fib_g(6)
    while True:
        try:
            x = next(g)
            print('g', x)
        except StopIteration as e:
            print('Generator return value:', e.value)
            break
    
    """
    g 1
    g 1
    g 2
    g 3
    g 5
    g 8
    Generator return value: done
    """

    4、生成器send方法

    send方法作用:
      1、唤醒并继续执行  (next方法只有这个功能)
      2、发送一个信息到生成器内部

    def range2(n):
    
        count = 0
        while count < n:
            print('count', count)
            count += 1
            sign = yield count
            if sign == 'stop':
                break
            print("---sign", sign)
        return 3333 
    
    
    new_range = range2(3)  # 0,1,2
    n1 = next(new_range)
    print(new_range)
    new_range.send("stop")  # 生成器不再执行后两步直接终止
    
    """
    count 0
    <generator object range2 at 0x10244c0f8>
    Traceback (most recent call last):
      File "/Users/.../函数生成器2.py", line 20, in <module>
        new_range.send("stop")
    StopIteration: 3333
    """

       如下所示为生成器与外部交互:

    def run():
        count = 0
        while True:
            n = yield count
            print("--", n, count)
            count += 1
    
    
    g =run()
    
    g.__next__()
    g.send("alex")
    g.send("egon")
    g.send("jack")
    """
    -- alex 0
    -- egon 1
    -- jack 2
    """

    5、通过yield实现单线程情况下实现并发运算的效果

    #_*_coding:utf-8_*_
    __author__ = 'Alex Li'
    
    import time
    def consumer(name):
        print("%s 准备吃包子啦!" %name)
        while True:
           baozi = yield
    
           print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
    
    
    def producer(name):
        c = consumer('A')
        c2 = consumer('B')
        c.__next__()
        c2.__next__()
        print("老子开始准备做包子啦!")
        for i in range(10):
            time.sleep(1)
            print("做了2个包子!")
            c.send(i)
            c2.send(i)
    
    producer("alex")
    
    通过生成器实现协程并行运算

    三、生成器总结

    1、生成器的创建方式   

      1.列表 列表生成式
      2.函数 函数生成器 yield

    2、yield 对比 return

      return返回并中止function
      yield返回函数,并冻结当前的执行过程
      函数有了yield之后
      (1)函数名加()就变成了一个生成器
      (2)return在生成器里,代表生成器的中止,直接报错

    3、next:唤醒生成器并继续执行

      next唤醒冻结的函数执行过程,继续执行,直到遇到下一个yield

    4、send("stop"):

      1.唤醒并继续执行
      2.发送一个信息到生成器内部

    5、python2和python3中range()方法对比

    在Python2中:
      range = list

    >>> range(10)
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

      xrange = 生成器

    >>> a = xrange(10)
    >>> type(a)
    <type 'xrange'>
    >>> for i in a:
    ...     print i
    ...
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

    在Python3中:
      range = genetator

    >>> range(10)  # range(0,10)  生成这个公式没有正式创建
    range(0, 10)
    >>> type(range(10))
    <class 'range'>

      xrange :python3中已取消该方法

    四、可迭代对象(Iterable)和迭代器(Iterator)

    1、什么是可迭代对象?什么是迭代器?

      可直接作用于for循环的数据类型有一下几种:

      一、集合数据类型,如:list、tuple、dict、set、str等;

      二、generator,包括生成器表达式(geneator expression)和生成器函数(generator function)两组构建方式。

      上述这些可以直接作用于for循环的对象统称为可迭代对象(Iterable)。可以使用isinstance()判断一个对象是否是Iterable对象。

    from collections import Iterable  # 可迭代类型
    # 使用isinstance()判断一个对象是否是可迭代对象
    isinstance('abc', Iterable)
    isinstance({}, Iterable)
    isinstance((x for x in range(10)), Iterable)
    
    isinstance(100, Iterable)  # 返回False

       迭代器定义:可以被next()函数调用并不断返回下一个值得对象称为迭代器(Iterator)

    2、可迭代对象与迭代器的关系

    生成器是迭代器的一种:

      1、生成器都是迭代器对象,但listdictstr虽然是可迭代对象,但不是迭代器。

      2、把listdictstr等可迭代对象变成迭代器可以使用iter()函数

    >>> from collections import Iterator
    >>> isinstance('abc', Iterator)  
    False
    >>> a = iter('abc')
    >>> a
    <str_iterator object at 0x10c584b38>
    >>> a.__next__()
    'a'
    >>> isinstance(iter([]), Iterator)
    True
    >>> isinstance(iter('abc'), Iterator)
    True

    3、为什么listdictstr等数据类型不是Iterator

      这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

      Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

    4、总结

      凡是可作用于for循环的对象都是Iterable类型;

      凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

      集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

      Python的for循环本质上就是通过不断调用next()函数实现的。 

    for x in [1, 2, 4, 5]:
        pass
    
    # 完全等价于
    # 获得Iterataor对象
    it = iter([1, 2, 3, 4, 5])
    # 循环
    while True:
        try:
            # 获得下一个值:
            x = next(it)
        except StopIteration:
            # 遇到StopIteration就退出循环
            break
  • 相关阅读:
    centos 用户指定目录访问
    centos FTP 用户指定目录禁用上级目录
    centos下SVN搭建多个库文件总汇
    listview点击checkbox,修改值
    C#转成时间格式
    nmap 查看主机上开放的端口
    xargs、管道、exec区别
    OSI七层模型,作用及其对应的协议
    linux面试题(重点)
    数据库备份还原 mysqldump
  • 原文地址:https://www.cnblogs.com/xiugeng/p/8600573.html
Copyright © 2011-2022 走看看