迭代对象、迭代器、生成器浅析

来源:互联网 发布:安卓软件开发编程论坛 编辑:程序博客网 时间:2024/05/29 03:04

迭代对象、迭代器、生成器浅析

这三者都是迭代对象,可通过for循环逐个获取对象元素。生成器基本不占用内存无论有多大数据量,但是只能使用一次(也可以通过一些途径使用多次)。

迭代对象 iterables

能一次返回一个元素的对象,主要用于for循环。

基本上python的所有容器(container)都是可迭代的,比如有序容器list, str, tuple,set 和无序容器dict, file,还有collections模块提供的几个亮瞎眼的容器namedtuple、OrderedDict、Counter、defaultdict、deque、ChainMap、UserDict、UserList、UserString。一般情况下,要扩展内置类型的功能需要通过子类化来实现,但轮子已被前辈造好,不仅性能好,而且使得代码更加简洁pythonic。

例子,Counter()用于统计元素出现的个数,是字典builtins.dict的子类。

通常做法:

s="afmldskmfl"occurrences = {}for _,e in enumerate(s):    occurrences1[e]=occurrences1.get(e,0)+1#返回{';': 1, 'a': 3, 'd': 1, 'f': 1, 'k': 1, 'l': 1, 's': 3}

pothonic写法:

occurrences = Counter(s)#返回Counter({';': 1, 'a': 3, 'd': 1, 'f': 1, 'k': 1, 'l': 1, 's': 3})occurrences.most_common(2)#返回[('s', 3), ('a', 3)]

Counter()除了继承dict的方法,自己又实现了三个,elements()、most_common()、subtract()。

可迭代对象本质上是实现了魔法方法__iter__()__getitem__(),该方法可以看做是所有迭代对象都要遵循的一个协议。所以,除了python内置的迭代对象,我们也可以在自己定义的class里通过重载__iter__()来获得可迭代性。

大多容器是可迭代对象,只有少数是迭代器。比如list就是个迭代对象,但如果调用了魔法方法__iter__(),则会变成一个迭代器。例子如下。

x = [1,2,3]isinstance(x, clc.abc.Iterable)#返回Trueisinstance(x, clc.abc.Iterator)#返回Falsex1 = iter(x)#或者用x1 = x.__iter__()isinstance(x1, clc.abc.Iterator)#返回True

迭代器 iterator

迭代器必定是可迭代对象。

所以迭代器是可迭代对象的子类,必须遵循迭代协议,即有方法__iter__(),同时自己也有方法__next__()

其实,__next__()基本是实现了迭代对象的for循环操作,但区别是next会耗尽迭代器,当到达元素末尾之后再调用next会出现StopIteration异常,同时也是一次性的,耗尽之后再没法使用。

而for循环接收迭代对象的StopIteration异常并以此为break条件,同时,for中使用迭代对象就完全可以工作了,没必要把迭代器放在for循环里,多此一举,而且最重要的是迭代对象不会耗尽。

一个迭代对象可通过魔法方法__iter__()转变为一个迭代器,即iterator=iterable.__iter__()iterator=iter(iterable)

自己实现一个计算Fibonacci数列的迭代器:

import collections as clcclass Fibonacci(object):    def __init__(self, n=10):        self.a = 0        self.b = 1        self.n = n    def __iter__(self):    """若存在此方法,python解释器会把实例化的对象当做迭代对象"""        return self    def __next__(self):    """若存在此方法,python解释器会把实例化的对象当做迭代器"""        if self.n == 0:            raise StopIteration        self.a, self.b = self.b, self.a+self.b        self.n -= 1        return self.bif __name__ == "__main__":    z = Fibonacci(5)#实例化对象    print(isinstance(z, clc.abc.Iterable))#输出True    print(isinstance(z, clc.abc.Iterator))#输出True    print(next(z))#输出1    print(z.__next__())#输出2    for f in z:        print(f)#输出3,5,8

虽然这不是实现Fibonacci数列很Pythonic的方式,但是可以了解迭代器的工作原理。

迭代器可以说是个鸡肋般的存在,远不如迭代对象用途广泛,近不如生成器那样高效简洁—-鲁迅。但他是生成器的基石。

生成器 generator

为什么叫生成器呢?其实是一种生成的迭代器(generate iterators),故也称generator iterators,简称generators,即生成器。

一个自定义生成器最显著的特征是yield语句。当解释器看到存在yield语句时,便知道这是个生成器定义。

普通函数用return返回,而生成器用yield作为名义上的返回。

当调用普通函数时,return语句前的所有代码全部被执行,然后返回return后的值;如果返回的是一个list,则所有值都会写入这个list存在于内存中。
当调用生成器时,每次调用会返回yield后面的值并把当前位置记住,下次再调用时就从这个位置开始。所以,yield语句可以写在一个无限循环里而不用提供终止函数的条件!因为每次返回一个值,所以基本不会占用内存,无论数据有多少。

其实,yield实现的功能与__iter__()+__next__()是一样的。很显然,要实现同一个功能,定义一个类比直接用一个普通函数(plain function)更复杂。而yield正好可以通过一个简单函数实现一个类才能干的事。

用生成器实现Fibonacci数列。

def fibonacci(n):    a, b = 0, 1    while True:        if n == 0:            raise StopIteration        yield b        a, b = b, a+b        n -= 1if __name__ == "__main__":    z = fibonacci(5)#调用函数,将生成器赋给变量z    print(isinstance(z, clc.abc.Iterable))#输出True    print(isinstance(z, clc.abc.Iterator))#输出True    print(next(z))#输出1    print(z.__next__())#输出2    for f in z:        print(f)#输出3,5,8

python标准库里自带的生成器有range()dict.items()zip()map()open等,功能都很强度,尤其是zip对于一次性迭代多个迭代对象时更是效率惊人,可参照我的前一篇文章8行代码实现ui文件到py文件转换。

总结

从范围来讲,迭代对象>迭代器>生成器

网上的一张图片很清晰地解释了三者之间的关系。
这里写图片描述

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(内容同步更新到微信公众号python数学物理,微信号python_math_physics
这里写图片描述

阅读全文
0 0
原创粉丝点击