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
  • 相关阅读:
    POJ 3258 (NOIP2015 D2T1跳石头)
    POJ 3122 二分
    POJ 3104 二分
    POJ 1995 快速幂
    409. Longest Palindrome
    389. Find the Difference
    381. Insert Delete GetRandom O(1)
    380. Insert Delete GetRandom O(1)
    355. Design Twitter
    347. Top K Frequent Elements (sort map)
  • 原文地址:https://www.cnblogs.com/xiugeng/p/8600573.html
Copyright © 2011-2022 走看看