zoukankan      html  css  js  c++  java
  • Python之迭代器和生成器

    Python 迭代器和生成器

    迭代器

    Python中的迭代器为类序列对象(sequence-like objects)提供了一个类序列的接口,迭代器不仅可以对序列对象(string、list、tuple)进行迭代,还可以对不是序列,但表现出序列行为的对象进行迭代,例如字典键、文件的行。

    迭代器对象有一个next()方法,调用后返回下一个条目。所有条目迭代完后,迭代器引发一个StopIteration异常告诉程序循环结束。for语句可用于序列类型,也可以用于迭代器类型,它会在内部调用next()并捕获异常。

    不过,迭代器也有一些限制,它不能向后移动,不能回到开始,也不能复制一个迭代器

    iter(obj)工厂函数可以返回一个迭代器,reversed()函数返回一个反序访问的迭代器,

    例如:

    t = (123, 'xyz', 45.67)
    it = iter(t)    # it = reversed(t) 函数返回一个反序访问的迭代器
    it.next()       # 123
    it.next()       # ‘xyz’
    it.next()       # 45.67
    it.next()       # StopIteration

    iter()方法的另一种使用方式是:iter(func, sentinel),它会重复调用func,直到迭代器的下个值等于sentinel。

    a = 1 
    
    def foo():
        global a
        print(a)
        a += 1
        return a
    
    it = iter(foo, 11) 
    for i in it: 
        pass

    上面的例子中,for循环会在i=11的时候停下来。

    我们知道,for语句接受可迭代对象(序列或迭代器)作为其参数,每次迭代其中一个元素。它会自动调用迭代器的next()方法,捕获StopIteration异常并结束循环,而这一切都是在内部发生的。例如:

    for c in "hello world" : print c

    上面是通过序列项迭代元素,下面介绍一种使用range函数进行序列索引迭代的方法。

    range函数返回一个list,它有三种调用形式:

    range(start, end, step) 
    range(start, end) # step = 1 
    range(end)     # start=0, step=1

    例如:

    for eachVal in range(2, 19, 3):
        print "value is: ", eachVal

    注意,当有有一个很大范围的列表时,xrange()可能更为适合,因为它不会在内存里创建列表的完整拷贝,它只能被用在for循环中,在for循环外使用没有意义。

    另外,xrange()返回的是一个xrange对象,这既不是一个序列对象,也不是一个迭代器对象。

    xrange()的用法跟range()完全一样:

    for eachVal in xrange(2, 19, 3):
        print "value is: ", eachVal

    但xrange不会返回一个list,而是每次循环返回一个值,性能更好些,后面会介绍生成器表达式,也是类似的原理。

    另外,在迭代可改变对象的时候不要修改它们。

    lst = range(10)
    print(lst)
    
    for ele in lst:
        ele = ele*3
    
    print(lst)

    可以发现,lst没有任何改变。

    如果要修改迭代的对象,可以使用索引迭代:

    lst = range(10)
    print(lst)
    
    for k in range(len(lst)):
        lst[k] = lst[k]*3
    
    print(lst)

    列表解析

    列表解析的语法:

    [expr for iter_var in iterable if cond_expr]

    返回一个列表。

    例如:

    seq = [1, 2, 3, 4 ,5, 6, 7, 8, 9]         
    [item for item in seq if item%2]      
    # [1, 3, 5, 7, 9]

    又比如打印一个3*5的矩阵:

    [(x+1, y+1) for x in range(3) for y in range(5)]

    生成器表达式

    生成器表达式与列表解析的用法一样,只是把"[]"换成了 “()”,但生成器表达式返回不是list,而是生成器(generator),生成器本质上是一种函数。

    生成器在每次计算出一个条目后,把这个条目yield出来,它使用了一种延迟计算(lazy evaluation),所以在内存上更有效。

    例如:

    >>> matrix = ((x+1, y+1) for x in range(3) for y in range(5))   
    >>> for i in matrix: print(i)
    ... 
    (1, 1)
    (1, 2)
    (1, 3)
    (1, 4)
    (1, 5)
    (2, 1)
    (2, 2)
    (2, 3)
    (2, 4)
    (2, 5)
    (3, 1)
    (3, 2)
    (3, 3)
    (3, 4)
    (3, 5)

    再比如,如果想得到一个很大的文本文件最长的行:

    max( len(line.strip()) for line in open('/etc/motd') )

    如果这里使用列表解析,那么不可避免需要把整个文件的所有行都加载到内存中,而是要生成器表达式在性能上会更好。

    生成器

    生成器的本质是一个带yield语句的函数,通常一个函数只能返回一次,而生成器能暂停执行并返回一个中间的结果(yield语句的功能),返回一个值给调用者并暂停执行。当生成器的next()方法被调用的时候,它会准确地从离开地方继续。

    可见,生成器跟协程的概念很类似,可以暂停或者挂起,并从程序离开的地方继续执行。

    rows = [1, 2, 3, 17]
    
    def cols():
        yield 56
        yield 2
        yield 1
    
    x_product_pairs = ((i,j) for i in rows for j in cols())
    
    for pair in x_product_pairs:
        print(pair)

    运行结果:

    (1, 56)
    (1, 2)
    (1, 1)
    (2, 56)
    (2, 2)
    (2, 1)
    (3, 56)
    (3, 2)
    (3, 1)
    (17, 56)
    (17, 2)
    (17, 1)

    当调用生成器的next()方法时,生成器会执行,直至出现yield语句,并把yield的参数返回给调用者(类似于return)。

    注意,yield语句后面的代码不会再运行。需要下次继续调用next()方法才会继续执行,直至函数退出。

    此外,调用者也可用将值回送给生成器(通过send()方法),以及要求生成器退出(close())。

    例子:

    def counter(start_at=0):
        count = start_at
        while True:
            val = (yield count)
            if val is not None:
                print("val is not None ", val)
                count = val 
            else:
                count += 1
    
    
    c = counter(5)
    print(c.next())    #5
    print(c.next())    #6
    print(c.next())    #7
    
    c.send(100)
    print(c.next())    #101
    
    c.close()
    c.next()           #StopIteration

    生成器带有一个初始化的值,每次调用生成器next()时对count累加1,用户可以send()重置这个值;而调用close()方法,会终结生成器。

  • 相关阅读:
    MyEclipse 常用快捷键
    javaEE基础08
    MySql卸载重新安装出现Start service没有响应的解决办法(64位)
    javaSE基础07
    为WAMP中的mysql设置密码(默认为空)
    javaSE基础06
    javaSE基础05
    vue框架构建项目流程
    阿里云或本地部署服务器(一)---nginx本地和服务器代理
    修改vue element Transfer 穿梭框里内容区的宽度
  • 原文地址:https://www.cnblogs.com/chenny7/p/4206369.html
Copyright © 2011-2022 走看看