• python设计模式之迭代器与生成器详解(五)


    前言

    迭代器是设计模式中的一种行为模式,它提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。python提倡使用生成器,生成器也是迭代器的一种。

    系列文章

    python可迭代对象和迭代器

    要点

    • 迭代即遍历,那么可迭代对象顾名思义就是可以遍历的数据类型或结构,表现在python中就是支持for循环遍历的对象。

    • python中有Iterable类代表可迭代对象,所有的可迭代对象都属于这个类;Iterator类表示迭代器,所有的迭代器对象都属于这个类;

    • 可迭代对象为什么可迭代?因为可迭代对象的内部实现了迭代器这种行为模式,其在python中的表现就是__iter__魔法方法。也就是说所有python内建的数据结构如str、list等预先已在定义结构时使用__iter__方法实现了迭代器.

    可迭代对象和迭代器的原理

    根据上面的要点我们自定义可迭代对象:

    from collections import Iterable
    
    class MyIterable(object):
    
        def __iter__(self):
            pass
    
    my_iter = MyIteradle()
    
    print(isinstance(my_iter,Iterable))
    
    # 结果:
    True
    

    说明python解释器是通过判断一个对象是否有__iter__魔法方法来确定是否是可迭代对象。现在我们尝试用for...in...遍历一下我们定义的可迭代对象:

    my_iter = MyIter()
    for i in my_iter:
        print(i)
    
    结果:
    TypeError: iter() returned non-iterator of type 'NoneType'
    

    报错了,为什么?我们需要知道for...in...干了什么事:

    python解释器遇到for...in关键字时,第一步找到in后面的my_iter对象,寻找内部的__iter__魔法方法,如果有就执行这个方法,该方法会生成一个迭代器;

    第二步从迭代器中取出一个值,并将这个值赋值给i.

    那么清楚了,上述我们虽然有了__iter__魔法方法,但是它并不会返回一个迭代器,从迭代器中取值这个动作也没有。那么我们需要实现一个迭代器。

    为了便于理解,我们把可迭代对象想象成一个容器,里面存放了我们的数据;迭代器想象成以可迭代对象为原型,在上面加装了一种方法可以顺序访问一个可迭代对象中各个元素,for循环干的事就是获取这个迭代器并从迭代器中取数据。

    记住:可迭代对象和它的迭代器是两个不同的对象。

    如此我们可知,既然迭代器的原型是可迭代对象,那么自然也要有__iter__魔法方法了,可是这个方法要求返回一个迭代器,那么不无限循环了吗?我们可以让其返回它自己就可以了。另外要加一个方法实现从迭代器中取数据啊,python解释器规定这个方法为_next_.

    from collections import Iterable, Iterator
    class MyIterator(object):
    
        def __iter__(self):
            return self
    
        def __next__(self):
            return 0
    my_iterator = MyIterator()
    print(isinstance(my_iterator, Iterator))
    for i in my_iterator:
        print(i)
    
    # 结果:
    True
    

    没有报错,由此我们可知在python中实现了__iter__和__next__方法的对象就是迭代器。

    完成了吗?并没有,看迭代器定义:提供一种方法顺序访问一个聚合对象中各个元素;顺序访问,由此有:

    class MyIterator(object):
    
        def __init__(self, mylist):
            self.mylist = mylist
            # current用来记录当前访问到的位置
            self.current = 0
    
        def __next__(self):
            if self.current < len(self.mylist):
                item = self.mylist[self.current]
                self.current += 1
                return item
            else:
                raise StopIteration
    
        def __iter__(self):
            return self
    

    显性获取和使用迭代器

    使用for...in...关键字,python解释器把获取迭代器和从迭代器中取值的过程全部自动完成了,如果我想手动一步步实现这个过程怎么办呢?python提供了显性的方法iter()和next().

    # 两种方法可以获取一个对象的迭代器
    l = [0,1,2]
    print(l.__iter__())
    print(iter(l))
    
    # 结果:
    <list_iterator object at 0x000002567EA5C518>
    <list_iterator object at 0x000002567EA5C518>
    
    • 手动遍历
    # 使用next方法取值
    l = [0,1,2]
    ter = iter(l)
    print(ter)
    while True:
        try:
            print(next(ter))
        except StopIteration:
            break
    

    生成器

    生成器是一类特殊的迭代器,什么意思?假如我们想自定义一个迭代器,那么我们需要手动实现__iter__和__next__方法,这显然太过麻烦,于是python为我们提供了一个简单快速的方法。

    def my_iterator(mylist):
        current = 0
        while current < len(mylist):
            res = mylist[current]
            current += 1
            yield res
        return '遍历完成'
    
    l = [0,1,2,3]
    F = my_iterator(l)
    for i in F:
        print(i)
    

    可以看到,我们把__next__方法中的逻辑抽出来,使用yield返回一个结果,这种简便的结构就是生成器了,本质上是一种快速获得迭代器的方法。

    此时my_iterator的return值通过for循环是获取不到的,而是需要StopIteration捕捉。

    l = [0,1,2,3]
    F = my_iterator(l)
    while True:
        try:
            next(F)
        except StopIteration as e:
            print("生成器返回值:%s"%e.value)
            break
    

    此时的next()函数可以唤醒生成器,另外我们可以使用send()方法来唤醒生成器,同时传递一个值到生成器中。

    def my_iterator(mylist):
        current = 0
        while current < len(mylist):
            res = mylist[current]
            current += 1
            i = yield res
            print(i)
        return '遍历完成'
    
    l = [0,1,2,3]
    F = my_iterator(l)
    while True:
        try:
            f.send('aaaa')
        except StopIteration as e:
            print("生成器返回值:%s"%e.value)
            break
    

    生成器生成式

    在逻辑足够简单的时候,一个更快捷的创建生成器的方法:

    f = (i for i in range(10)) # 此时f表示的不是元组而是生成器
    

    总结

    1. 可迭代对象的定义时是对象定义了__iter__方法,迭代器的定义是对象实现了__iter__和__next__魔法方法;

    2. 迭代遍历停止的标志是抛出StopIteration异常,for循环会自动捕捉这个异常停止迭代;

    参考

    • 作者:天宇之游
    • 出处:http://www.cnblogs.com/cwp-bg/
    • 本文版权归作者和博客园共有,欢迎转载、交流,但未经作者同意必须保留此段声明,且在文章明显位置给出原文链接。
  • 相关阅读:
    Reverse Bits
    Jump Game
    Valid Palindrome
    【计算几何初步-线段相交】【HDU1089】线段交点
    【数位DP】【HDU2089】不要62
    【二分图最大匹配】【HDU2063】过山车
    【分割平面,分割空间类题】【HDU1290 HDU2050】
    【特殊的图+DP】【11月校赛】大家一起玩游戏
    【考虑周全+数学变形】【11月赛】Is it a fantastic matrix?
    【进制问题】【HDU2056】A + B Again
  • 原文地址:https://www.cnblogs.com/cwp-bg/p/9530810.html
Copyright © 2020-2023  润新知