• Pyhon中迭代器与生成器


    迭代器

    我们知道,可以直接用for循环的数据类型有以下几种:

    一类是集合数据类型:list、tuple、dict、set、str等

    一类是generator:包括生成器和带yield的generator function

    这些可以直接用作与for循环的对象统称为可迭代对象:Iterable

    可以使用isinstance()判断一个对象是否是Iterable对象

     1 from collections import Iterable
     2 isinstance([], Iterable)
     3 True
     4 
     5 isinstance({}, Iterable)
     6 True
     7 
     8 isinstance('abc', Iterable)
     9 True
    10 
    11 isinstance((x for x in range(10)), Iterable)
    12 True
    13 
    14 isinstance(100, 

    而生成器不但可以作用域for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

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

    可以使用instance()判断一个对象是否是Iterator对象:

     1 from collections import Iterator
     2 isinstance(((x for x in range(10)),Iterator)
     3 True
     4 
     5 isinstance([], Iterator)
     6 False
     7 
     8 isinstance({}, Iterator)
     9 False
    10 
    11 isinstance('abc', Iterator)
    12 False

    生成器都是Iterato对象,但list、dict、str虽然是Iterable,却不是Iterator,把list、dict、str/等Iterable变成Iterator可以使用iter()函数:

    1 isinstance(iter([]), Iterator)
    2 True
    3 
    4 isinstance(iter('abc'), Iterator)
    5 True

    为什么list、dict、str等数据类型不是Iterator呢?

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

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

    小结:

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

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

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

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

     1 for x in [1,2,3,4,5]:
     2     pass
     3 
     4 #实际上完全等价于:
     5 it = iter([1,2,3,4,5])
     6 while True:
     7     try:
     8     x = next(it)     #获取下一个值
     9     except StopIteration:  #遇到StopIteration退出循环
    10     break

    生成器

    通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

    所以,如果裂变元素可以按照某种算法推导出来,那我们是否可以在循环过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省很大的空间。在Python中,这种一边循环一边计算的机制,成为生成器:generator。

    要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

    L = [x *x for x in range(10)]
    g = (x*x fox x in range(10)]

    如果要打印generator的每一个元素,可以通过next()函数

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

    当然用next()的方法是在太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

    g = (x*x for x in range(10))
    for n in g:
        print(n)

    所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

    generator非常的强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

    例如著名的斐波拉契数量(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

    1,1,2,3,5,8,13,21,34......

    1 def fib(max):
    2     n, a, b = 0, 0, 1
    3     while n < max:
    4         print(b)
    5         a, b = b, a + b
    6         n = n + 1
    7     return done
    t = (b, a + b)
    a = t[0]
    b = t[1]

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

    也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改成yield b就可以了

    1 def fib(max):
    2     n, a, b = 0, 0, 1
    3     while n < max:
    4         yield b
    5         a, b = b, a + b
    6         n = n + 1
    7     return 'done'

    这就是定义generator的另一个种方法,如果一个函数中包含yield关键字,那么这个函数就不在是一个普通函数而是一个generator。

    这里最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句出继续执行。

    如果用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想拿到返回值,必须捕获StopIteration错误,返回值含在StopIteration的value中。

    g = fib(6)
    while True:
        try:
            x = next(g)
            print('g:', x )
        except StopIteration as e:
            print('Generator return value:'e.value)
            break

    generator是非常强大的工具,在Python中,可以简单地把列表生成式改成generator,也可以通过函数实现复杂逻辑的generator。

    要理解generator的工作原理,它是在for循环的过程中不断计算下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就结束generator的指令,for循环随之结束。

    列表生成式

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

    例如要生成list[1,2,3,4,5,6,7,8,9]可以用list(range(1,11))

    但如果要生成[1x1,2x2,3x3,4x4....,10x10]怎么做呢:

    1 #方法1 for循环
    2 L =  []
    3 for x in range(1,11):
    4     L.append(x * x)
    5 
    6 #方法2,列表生成式
    7 [x * x for x in range(1,11)]

    写成列表生成式时,把要生成的元素x * x放在前面,后面跟for循环,就可以把list创建出来。

    for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

    1 [ x * x for x in range(1, 11) if x % 2 == 0]
    2 [4,16,36,64,100]

    还可以使用两层循环,可以生成全排列:

    [m + n for m in 'ABC' for n in 'XYZ']
    ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

    三层和三层以上的循环就很少用到了。

    运用列表生成式,可以写出非常简洁的代码。例如列出当前目录下的所有文件和目录名,可以通过一行代码实现:

    import os
    [d for d in os.listdir('.')】

    for循环其实可以同时使用两个甚至多个变量,比如dict的items可以同时迭代key和value:

    d = {'x':'a','y':'b','z':'c'}
    for k, v in d.items():
        print(k, '=', v)

    列表生成式也可以使用两个变量来生成list:

    d = {'x':'a', 'y':'b', 'z':'c'}
    [k + '=' + v for k, v in d.items()]
  • 相关阅读:
    zoj 3627#模拟#枚举
    Codeforces 432D Prefixes and Suffixes kmp
    hdu 4778 Gems Fight! 状压dp
    CodeForces 379D 暴力 枚举
    HDU 4022 stl multiset
    手动转一下田神的2048
    【ZOJ】3785 What day is that day? ——KMP 暴力打表找规律
    poj 3254 状压dp
    C++中运算符的优先级
    内存中的数据对齐
  • 原文地址:https://www.cnblogs.com/eric_yi/p/7277361.html
Copyright © 2020-2023  润新知