zoukankan      html  css  js  c++  java
  • Python Iterator and Generator

    Python Iterator and Generator

    Iterator

    迭代器(Iterator)和可迭代对象(Iterable)往往是绑定的。可迭代对象就是我们平时经常用的list ,string, tuple这种。事实上迭代器的概念会比可迭代对象宽泛很多,一会举几个例子就能明白。

    ​ 在使用list这种数据类型的时候,我们经常会使用下面这种迭代方式:

    # eg 1
    mylist = [1,2,3,4]
    for x in mylist:
    	print(x)
    >>>1
    >>>2
    >>>3
    >>>4
    

    ​ 有时候会很奇怪for循环为什么可以这么用,列表的索引竟然会自动往后一个一个走,走到结尾了,还会自动停下来,不会有list out of range的报错。神奇。其实for循环做的事情不止这么简单。要说明这个问题,得先说迭代器具体怎么做,是什么。

    ​ 要创建一个迭代器,就必须要实现两个方法,分别是__iter__()__next__(),以及实现异常机制StopIteration。请看下面的例子:

    # eg 2
    class PowTwo:
        """Class to implement an iterator of powers of two"""
    
        def __init__(self, max = 0):
            self.max = max
    
        def __iter__(self):
            self.n = 0
            return self
    
        def __next__(self):
            if self.n <= self.max:
                result = 2 ** self.n
                self.n += 1
                return result
            else:
                raise StopIteration
    

    ​ 可以看到,迭代器是通过写一个类来定义的,类里面实现了刚刚所说的__iter__()__next__()方法。这个迭代器是用来产生一系列2的指数次方的数字的,具体用法看下面:

    a = PowTwo(4)
    i = iter(a)			# attention please
    next(i)
    >>>1
    next(i)
    >>>2
    next(i)
    >>>4
    next(i)
    >>>8
    next(i)
    >>>16
    next(i)
    >>>Traceback (most recent call last):
    	...
    	StopIteration
    

    ​ 仔细看哦,第一行代码用4创建了一个实例a,设置了这个迭代器的迭代上限,然后并不是直接用这个实例就可以了,还得调用iter()函数去把a彻底进化成一个Iterator,进而有了接下来next()函数的闪亮登场。其实我也蛮奇怪,直接用这个类不好么,比如下面的代码:

    a = PowTwo(4)
    a.__iter__()
    a.__next__()
    >>>1
    a.__next__()
    >>>2
    a.__next__()
    >>>4
    a.__next__()
    >>>8
    a.__next__()
    >>>16
    next(i)
    >>>Traceback (most recent call last):
    	...
    	StopIteration
    

    ​ 完全没问题,但是你自己比较一下二者的代码,哪个美一点毋庸置疑。。。再说了,跟装饰器一样,一切为了简洁优美而生嘛。

    ​ OK,可以回到最初的起点——for循环了。示例1中的代码,for循环到底做了什么呢,答案是,for循环其实是先把mylist变成迭代器,然后用while循环去迭代:

    iter_obj = iter(mylist)
    while True:
        try:
            element = next(iter_obj)
        except StopIteration:
            break
    

    ​ 这样一路解释过来,应该就不难理解迭代器了。总结一下就是:

    1. 如何已经有一个可迭代对象,那么直接用iter()函数去初始化它,然后疯狂next()即可;
    2. 如果没有现成的可迭代对象,那就自己写一个类,类里面记得实现__iter__()__next__()方法,以及异常机制StopIteration,然后操作同1;
    3. 如果你想要一个无限迭代器,不要实现异常机制StopIteration即可。



    Generator

    ​ 这玩意儿就比迭代器复杂点了,所以还得分几个小点,逐个击破。

    1. 生成器是啥

    ​ 生成器也是Python中面向一系列需要迭代的问题,常用的解决方案。既然有了迭代器,可以解决很多迭代的问题,那为啥子还要生成器勒?

    ​ 主要的原因是迭代器的开销太大了。对于一些小问题还好,大的问题需要迭代的元素很庞大的时候,迭代器就使不上劲儿了。而且,创建一个迭代器,说实话也还挺麻烦的,看看上面的小总结的第二点,你得实现这些方法和手动处理异常。

    ​ 而且迭代器要写类,其实一个函数可以搞定的事情,何必那么复杂。正是应了这个景,生成器也就是在函数对象上搞事情的。

    ​ 这个原因点到即止,先把生成器讲清楚了,自然就通透了。先看一个小例子,写一个生成器函数(Generator Function):

    # eg 1
    def my_gen():
        n = 1
        print('This is printed first')
        yield n
    
        n += 1
        print('This is printed second')
        yield n
    
        n += 1
        print('This is printed at last')
        yield n
    

    ​ 上面就是一个简单的生成器函数,多了一种关键字yield,细心看会发现这个函数竟然没有return!再细看代码,没错,yield替代了return。那怎么用呢,有两种用法如下:

    # usage 1
    a = my_gen()
    next(a)
    >>>This is printed first
    	1
    next(a)
    >>>This is printed second
    	2
    next(a)
    >>>This is printed at last
    	3
    next(a)
    >>>Traceback (most recent call last):
    	...
    	StopIteration
    
    # usage 2	
    for item in my_gen():
        print(item) 
    >>>
    This is printed first
    1
    This is printed second
    2
    This is printed at last
    3
    

    ​ 对于用法1,把函数赋值给了a,然乎疯狂next()即可。你会发现,我们并没有像迭代器那样实现__iter__()__next__()方法,以及异常机制StopIteration,只是用了一个yield关键字,这个生成器函数却达到了迭代器一样的效果。

    ​ 对于用法2,更牛皮了,甚至不用赋值的操作,直接for这个生成器函数。。。

    2. 循环机制生成器

    ​ 刚刚那个小函数,就是一个最普通的例子,那问题是如果有多个n想要玩,岂不是来多少手动写多少?那当然还有循环的玩法,带有循环机制的生成器。下面是一个逆序输出的小例子:

    # eg 2
    def rev_str(my_str):
        length = len(my_str)
        for i in range(length - 1,-1,-1):
            yield my_str[i]
    
    for char in rev_str("hello"):
         print(char)
    >>>o
    >>>l
    >>>l
    >>>e
    >>>h
    

    ​ 没错,真无聊,犯得着逆序输出的程序还得上生成器么,犯不着,但是,只是想给出这个循环机制生成器的概念。如果你发现这个rev_str()函数和普通的逆序函数完全一样,只是return换成了yield,那就万事大吉,要理解的就是这个。就这样一记简单的替换操作,你就得到了一个生成器,是不是比迭代器省事儿多了。

    3. 生成器表达式(Generator Expression)

    ​ 不知道你有没有用过列表生成式,没用过也应该看到过,这类似于匿名函数,语法简洁,比如:

    # eg 3
    my_list = [1, 3, 6, 10]
    
    [x**2 for x in my_list]
    >>>[1, 9, 36, 100]
    

    ​ 生成器表达式和这个几乎一样,不信你看:

    # eg 4
    my_list = [1, 3, 6, 10]
    
    a = (x**2 for x in my_list)
    next(a)
    >>>1
    next(a)
    >>>9
    next(a)
    >>>36
    next(a)
    >>>100
    

    ​ 把列表生成式的[]直接改成(),就得到了一个生成器。

    4. Why Generator???

    (1). 简洁

    ​ 回到最开始迭代器的那个类的例子,用生成器咋写呢?

    def PowTwoGen(max = 0):
        n = 0
        while n < max:
            yield 2 ** n
            n += 1
    

    ​ 简洁吧,然后你就可以用这个生成器函数遨游了。

    (2). 开销小

    ​ 同样的一个需要迭代的功能,如果用普通函数写,一旦需要迭代的元素特别多,在使用的时候,普通函数需要等所有的元素计算出来了,然后把返回值给你。生成器就不是了,它一次计算出一个,用的时候就取一个,并且它还会记住位置,下次用就计算下一个,这样对空间的开销也是很小的。

    (3). 无限

    ​ 看下面的函数:

    def all_even():
        n = 0
        while True:
            yield n
            n += 2
    

    ​ 写普通函数,你必然做不到写出一个可以无限操作的函数,生成器却可以。(迭代器也可以,就是麻烦点儿)

    5. 再给一个例子

    # 利用yield生成一个斐波那契数列的生成器
    def fib(max):               
        n,a,b=0,0,1
        while n<max:
            yield b
            a,b=b,a+b
            n+=1
        return 'done'	# 要不要这句话都行
    f=fib(6)
    next(f)				# 疯狂next()它
    >>>
    1
    1
    2
    3
    5
    8
    Traceback (most recent call last):
    ...
    StopIteration: done
    
  • 相关阅读:
    HDU_5372 树状数组 (2015多校第7场1004)
    《 字典树模板_递归 》
    《神、上帝以及老天爷》
    《Crazy tea party》
    UVA_ Overflow
    UVA_If We Were a Child Again
    UVA_Product
    UVA_Integer Inquiry
    你也可以屌到爆的这样敲代码当黑客!
    大数相加_原创
  • 原文地址:https://www.cnblogs.com/machine-lyc/p/11332358.html
Copyright © 2011-2022 走看看