zoukankan      html  css  js  c++  java
  • 【python】迭代器&生成器

    源Link:http://www.cnblogs.com/huxi/archive/2011/07/01/2095931.html

    迭代器

    迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么, 因为人们很少在迭代途中往后退。另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件

    特点:

    1. 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
    2. 不能随机访问集合中的某个值 ,只能从头到尾依次访问
    3. 访问到一半时不能往回退
    4. 便于循环比较大的数据集合,节省内存
    class listiterator(object)
     |  Methods defined here:
     |  
     |  __getattribute__(...)
     |      x.__getattribute__('name') <==> x.name
     |  
     |  __iter__(...)
     |      x.__iter__() <==> iter(x)
     |  
     |  __length_hint__(...)
     |      Private method returning an estimate of len(list(it)).
     |  
     |  next(...)
     |      x.next() -> the next value, or raise StopIteration
    
    listiterator
    View Code

    生成一个迭代器:

    #在list中有个__iter__方法,就是个迭代器,其类里必须有个next方法。需要通过遍历来具体输出
    
    others = iter(['liupeng','tony','jack','rain']) #iter代表生成的是个迭代器。
    
    print(others)
    
    #Result(下面结果为一个迭代器(list_iteratior))
    
    <list_iterator object at 0x7efd6f8cbcc0>
    
    #others[2]  # 假如我们想取'jack'这个值,如果是列表或者元组的话,通过下标就可以读取元素。但是迭代器不可以只能通过__next__()持续迭代下去到这个值才能被取出。直接用下标取值会出现一个“TypeError: 'list_iterator' object is not subscriptable”的报错。
    
    print(others.__next__())          #迭代器一次只能迭代一个值,而且是从头开始迭代。不会重复迭代。要想读取到我们想找到的'jack',只能在第三次迭代中取出。
    print(others.__next__())
    print(others.__next__())
    print(others.__next__())
    print(others.__next__())          # 上述我们自己创建的迭代器中一共只有4个元素,如果超过迭代器元素的数量继续迭代的话,它会自动返回“StopIteration”的信息来告诉你已经到终点了。
    
    #Result
    liupeng
    tony
    jack
    rain
    Traceback (most recent call last):
      File "/home/liupeng/PycharmProjects/untitled/zhengzebiaoda.py", line 80, in <module>
        print(others.__next__())      # 上述我们自己创建的迭代器中一共只有4个元素,如果超过迭代器元素的数量继续迭代的话,它会自动返回“StopIteration”的信息来告诉你已经到终点了。
    StopIteration
     
    obj = iter([11,22,33,44,55,66,77,88,99,90])      #
    
    for n in obj:
        print(n)
    
    #Result   (利用for 循环取出iter中每个元素。)
    11
    22
    33
    44
    55
    66
    77
    88
    99
    90
    
    #下面这种情况其实是抛出Stoplteration异常。事实上,Python正是根据是否检查到这个异常来决定是否停止迭代的。 
    #这种做法与迭代前手动检查是否越界相比各有优点。但Python的做法总有一些利用异常进行流程控制的嫌疑。 
    #了解了这些情况以后,我们就能使用迭代器进行遍历了。 
    
    obj = iter([11,22,33,44,55,66,77,88,99,90])
    
    try:
        while True:
            val = obj.__next__()
            print (val)
    
    except StopIteration:
        pass
    
    #另外在使用迭代器的循环可以避开索引,但有时候我们还是需要索引来进行一些操作的。这时候内建函数enumerate就派上用场咯,它能在iter函数的结果前加上索引,以元组返回,用起来就像这样:
    
    obj =iter(['liupeng','tony','jack','rain'])
    
    for i in enumerate(obj):
        print(i)
    
    #Result
    (0, 'liupeng')
    (1, 'tony')
    (2, 'jack')
    (3, 'rain')
    实例1:
    obj = iter([11,22,33,44,55,66,77,88,99,90])
    
    while True:
        val = obj.__next__()
        print(val)
    
    #Result   (while循环的话判断为真,赋一个变量。然后把每次通过obj.__next__()的结果赋值给变量打印出来。当循环超过iter中的值后条件就为Talse,那么就会报StopIteration的错误。意思就是已经没值了到底了)
    22
    44
    66
    88
    90
    Traceback (most recent call last):
      File "/home/liupeng/PycharmProjects/untitled/zhengzebiaoda.py", line 77, in <module>
        val = obj.__next__()
    StopIteration
    实例2:

    使用迭代器一个显而易见的好处就是:每次只从对象中读取一条数据,不会造成内存的过大开销。

    例如:
    /* 把文件一次加载到内存中,然后逐行打印。当文件很大时,这个方法的内存开销就很大了 */
    for line in open("test.txt").readlines():
        print line
    
    /* 这是最简单也是运行速度最快的写法,他并没显式的读取文件,而是利用迭代器每次读取下一行 */
    for line in open("test.txt"):   #use file iterators
        print line
    

     生成器

    定义:一个函数调用时返回一个迭代器,那这个函数就叫做生成器(generator),如果函数中包含yield语法,那这个函数就会变成生成器

    • 生成器是一个特殊的程序,可以被用作控制循环的迭代行为
    • 生成器类似于返回值为数组的一个函数,这个函数可以接收参数,可以被调用,但是,不同于一般的函数会一次性返回包含了所有数值的数组,生成器一次 只产生一个值,这样消耗的内粗数量大大减少,而且允许调用函数可以很快的开始处理前几个返回值。因此,生成器看起来像一个函数但是表现的却像一个迭代器。
    python中的生成器
    python提供了两种基本的方式。
    • 生成器函数:也是用def来定义,利用关键字yield一次返回一个结果,阻塞,重新开始
    • 生成器表达式:返回一个对象,这个对象只有在需要的时候才产生结果

    下面详细讲解。

    生成器函数

    为什么叫生成器函数?因为他随着时间的推移生成了一个数值队列。一般的函数在执行完毕之后会返回一个值然后退出,但是生成器函数会自动挂起,然后重新拾起继续执行,他会利用yield关键字关起函数,给调用者返回一个值,同时保留了当前的足够多的状态,可以使函数继续执行。生成器和迭代协议是密切相关的,可迭代的对象都有一个__next()__成员方法,这个方法要么返回迭代的下一项,要么引起异常结束迭代。
    为了支持迭代协议,拥有yield语句的函数被编译为生成器,这类函数被调用时返回一个生成器对象,返回的对象支持迭代接口,即成员方法__next()__继续从中断处执行执行。
    看下面的例子:

    def creat_counter(n):
        print('create counter')
    
        while True:
            yield n
            print('increment n')
            n += 1
    
    cnt = creat_counter(2)
    print(cnt)
    
    print(next(cnt))
    print(next(cnt))
    print(next(cnt))
    

     分析一下这个例子:

    • 在create_counter函数中出现了关键字yield,预示着这个函数每次只产生一个结果值,这个函数返回一个生成器(通过第一行输出可以看出来),用来产生连续的n值
    • 在创造生成器实例的时候,只需要像普通函数一样调用就可以,但是这个调用却不会执行这个函数,这个可以通过输出看出来
    • next()函数将生成器对象作为自己的参数,在第一次调用的时候,他执行了create_counter()函数到yield语句,返回产生的值2
    • 我们重复的调用next()函数,每次他都会从上次被挂起的地方开始执行,直到再次遇到了yield关键字

    为了更加深刻的理解,我们再举一个例子。

    #coding
    def cube(n):
        for i in range(n):
            yield i ** 3
    
    for i in cube(5):
        print i
    
    #output
    1
    27
    

    所以从理解函数的角度出发我们可以将yield类比为return,但是功能确实完全不同,在for循环中,会自动遵循迭代规则,每次调用next()函数,所以上面的结果不难理解。

    生成器表达式:

    生成器表达式来自于迭代和列表解析的组合,生成器表达式和列表解析类似,但是他使用尖括号而不是方括号括起来的。如下代码:

    >>> # 列表解析生成列表
    >>> [ x ** 3 for x in range(5)]
    [0, 1, 8, 27, 64]
    >>> 
    >>> # 生成器表达式
    >>> (x ** 3 for x in range(5))
    <generator object <genexpr> at 0x000000000315F678>
    >>> # 两者之间转换
    >>> list(x ** 3 for x in range(5))
    [0, 1, 8, 27, 64]
    

     就操作而言,生成器表如果使用大量的next()函数会显得十分不方便,for循环会自动出发next函数,所以可以按下面方式使用:

    >>> for n in (x ** 3 for x in range(5)):
        print('%s, %s' % (n, n * n))
    
        
    0, 0
    1, 1
    8, 64
    27, 729
    64, 4096
    

    两者比较

    一个迭代既可以被写成生成器函数,也可以被协程生成器表达式,均支持自动和手动迭代。而且这些生成器只支持一个active迭代,也就是说生成器的迭代器就是生成器本身。

    协程与yield表达式

    yield语句还有更给力的功能,作为一个语句出现在赋值运算符的右边,接受一个值,或同时生成一个值并接受一个值。

    def recv():
        print ('Are your ready:')
        while True:
            n=yield
            print ('总共用了 %s 秒'%n)
    
    c = recv()
    c.__next__()
    c.send(100)
    c.send(300)
    

    以这种方式使用yield语句的函数称为协程。在这个例子中,对于__next__的初始调用是必不可少的,这样协程才能执行可通向第一个yield表 达式的语句。在这里协程会挂起,等待相关生成器对象send()方法给它发送一个值。传递给send()的值由协程中的yield表达式返回。

    协程的运行一般是无限期的,使用方法close()可以显式的关闭它。

    如果yield表达式中提供了值,协程可以使用yield语句同时接收和发出返回值。

    def split_line():
        print('ready to split')
        result = None
    
        while True:
            line = yield result
            result = line.split()
    
    
    s = split_line()
    s.__next__()
    print(s.send('1,2,3'))
    

     注意:理解这个例子中的先后顺序非常重要。首个next()方法让协程执行到yield result,这将返回result的值None。在接下来的send()调用中,接收到的值被放到line中并拆分到result中。send()方法 的返回值就是下一条yield语句的值。也就是说,send()方法可以将一个值传递给yield表达式,但是其返回值来自下一个yield表达式,而不 是接收send()传递的值的yield表达式。

    如果你想用send()方法来开启协程的执行,必须先send一个None值,因为这时候是没有yield语句来接受值的,否则就会抛出异常。

    >>> s=split_line()
    >>> s.send('1 2 3')
    TypeError: can't send non-None value to a just-started generator
    >>> s=split_line()
    >>> s.send(None)
    ready to split
    

    使用生成器与协程

    乍看之下,如何使用生成器和协程解决实际问题似乎并不明显。但在解决系统、网络和分布式计算方面的某些问题时,生成器和协程特别有用。实际上,yield已经成为Python最强大的关键字之一。

    比如,要建立一个处理文件的管道:

    import os,sys
    def default_next(func):
        def start(*args,**kwargs):
            f=func(*args,**kwargs)
            f.__next__()
            return f
        return start
    @default_next
    def find_files(target):
        topdir=yield
        while True:
            for path,dirname,filelist in os.walk(topdir):
                for filename in filelist:
                    target.send(os.path.join(path,filename))
     
    @default_next
    def opener(target):
        while True:
            name=yield
            f=open(name)
            target.send(f)
         
    @default_next
    def catch(target):
        while True:
            f=yield
            for line in f:
                target.send(line)
                 
    @default_next
    def printer():
        while True:
            line=yield
            print line
    

     然后将这些协程连接起来,就可以创建一个数据流处理管道了:

    finder=find_files(opener(catch(printer())))
    finder.send(toppath)
    

     程序的执行完全由将数据发送到第一个协程find_files()中来驱动,协程管道会永远保持活动状态,直到它显式的调用close()。

    总之,生成器的功能非常强大。协程可以用于实现某种形式的并发。在某些类型的应用程序中,可以用一个任务调度器和一些生成器或协程实现协作式用户空 间多线程,即greenlet。yield的威力将在协程,协同式多任务处理(cooperative multitasking),以及异步IO中得到真正的体现。

    有的时候真的是“眼观千遍,不如手动一遍”来的记忆犹新。

    此文截取多篇博客中的案例结合而制,同时以上代码都已经验证过有效。

    “今天的努力都是明天别人对你的膜拜,今天的停滞就是明天别人对你的唾弃!“



  • 相关阅读:
    【Java学习】Intellij IDEA基本配置
    【Java学习】Integer、new Integer() 和 int 比较和相关的面试题
    【Java学习】String[] args和String args[]的区别在哪里?
    【Java学习】包装类
    【Java学习】Java 枚举(enum)
    【Java学习】eclipse与intellij idea的区别
    【Mysql学习】mysql远程连接:ERROR 1130 (HY000): Host '*.*.*.*' is not allowed to connect to this MySQL server解决办法
    【Mysql学习】Mysql安装
    qplot函数用法(转载)
    webservice部署到服务器报错
  • 原文地址:https://www.cnblogs.com/liupengpengg/p/5607601.html
Copyright © 2011-2022 走看看