python迭代器

可迭代对象

        在python中我们知道很多可迭代对象,例如字符串、列表、元组等类型的对象。那这些可迭代对象是为什么可以被迭代的呢?因为它们内部定义了__iter__方法,能使用__iter__方法返回迭代器。当我们要迭代一个对象的时候,首先就会调用对象的__iter__方法返回迭代器,然后再对这个迭代器做迭代动作。只有定义了__iter__方法的对象才是可迭代对象,如果对象中未定义__iter__方法,那它不会被python认定为可迭代对象。

迭代器

        既然可迭代对象是因为可以返回迭代器,才被称为可迭代对象的。那么迭代器又是什么呢?迭代器拥有被迭代的能力,python中使用next函数来迭代迭代器。如果一个对象想要被next函数使用,那它的必须定义__next__方法来返回迭代值。

        所以迭代器中同时拥有__iter__方法和__next__方法。__next__方法用于迭代迭代器,当迭代器被next函数使用时,会调用迭代器中的__next__方法来返回值。所以迭代器的原理就是使用__iter__方法返回自己,使用__next__方法迭代自己。现在我们就来好好的了解__iter__和__next__这两个方法。

__iter__方法

        __iter__方法使用来返回迭代器的魔法方法,如果我们想一个对象可以被迭代,就需要在__iter__中返回迭代器。如果我们返回的不是迭代器,那这个对象虽然是可迭代对象,但任然不可被迭代。

class Iter:

    def __iter__(self):
        return 'abcdef'


for i in Iter():
    print(i)

Iter中虽然定义了__iter__方法,但__iter__返回的是字符串,字符串是可迭代对象不是迭代器,所以Iter的实例任不可被迭代。执行结果如下:

报错结果显示:iter()返回的是字符串不是迭代器。所以__iter__中需要返回迭代器,python内置函数iter()可以把可迭代对象变成迭代器,所以我们可以使用iter()来返回迭代器。

class Iter:

    def __iter__(self):
        return iter('abcdef')


for i in Iter():
    print(i)

 执行结果如下:

现在我们就又多了一种可迭代类型Iter。

__next__方法

        __next__方法用来迭代迭代器,当前对象被next函数使用时,会调用__next__方法返回迭代值。为了避免无限迭代,我们需要设置迭代条件,满足条件才迭代,不满足条件时我们要抛出StopIteration错误来终止迭代。至于为什么是抛出StopIteration错误,是因为for循环中是通过捕获StopIteration错误来停止迭代的。如果我们定义的迭代器想要被for循环使用,就必须是抛出StopIteration错误。

class Iter:
    number = 0

    def __next__(self):
        self.number += 1
        return self.number


iter1 = Iter()
print(next(iter1))
print(next(iter1))
print(next(iter1))
print(next(iter1))
print(next(iter1))

__next__中没有设置限制条件,Iter的实例iter1可以被无数多个next函数使用,取之不尽用之不完。执行结果如下:

如果我们想限制迭代次数,就必须在__next__中设置限制条件,及时的抛出StopIteration错误。

class Iter:
    number = 0

    def __next__(self):
        self.number += 1
        if self.number < 3:
            return self.number
        raise StopIteration


iter1 = Iter()
print(next(iter1))
print(next(iter1))
print(next(iter1))

执行结果如下:

现在迭代次数就不再是无限的了,而是被我们控制在了一定的范围内,超出范围就会报错。

__iter__ & __next__

        __iter__拥有返回迭代器的能力,但并不具备迭代的能力。__next__拥有迭代的能力,但并不是可迭代对象。那它们结合在一起,互相取长补短呢?就好比倚天剑碰上屠龙刀,会产生一个强大的东西——迭代器。

class Iter:

    def __init__(self, number):
        self.number = number
        self.index = 0

    def __iter__(self):  # 使用__iter__返回自己
        return self

    def __next__(self):  # 使用__next__迭代自己
        if self.index < self.number:
            value = self.index
            self.index += 1
            return value
        raise StopIteration


for i in Iter(5):
    print(i)

执行结果如下:

我们看到上面的Iter是不是很熟悉,就像range函数一样。但range函数并不是迭代器,而是一个容器。range的实例生成了一系列的数字,所以range的实例是比较占内存的。而迭代器占用的内存很小,因为迭代器中只有几个属性值,不会保存每次的迭代值。现在我们可以自行尝试制作类似range功能的迭代器,来减少内存的消耗。

实现range生产数字功能

        range函数生成数字的原理图:

        range函数有3种传参方式,第一种只传一个值(stop),第二种传两个值(start, stop),第三种传三个值(start, stop, step)。所以我们的__init__方法必须是一个多态方法,使用overload装饰函数设计出多态的__init__方法。

from typing import overload


class MyRange:
    @overload
    def __init__(self, stop: int):
        ...

    @overload
    def __init__(self, start: int, stop: int):
        ...

    @overload
    def __init__(self, start: int, stop: int, step: int):
        ...

    def __init__(self, *args: int):
        self.__start, self.__stop, self.__step = 0, 0, 1
        if len(args) == 1:
            self.__stop = args[0]
        elif len(args) == 2:
            self.__start, self.__stop = args
        elif len(args) == 3:
            self.__start, self.__stop, self.__step = args
        else:
            raise TypeError(f'MyRange expected at most 3 arguments, got {len(args)}')
        if self.__step == 0:
            raise ValueError('step must not be 0')

 使用overload装饰的只是多态方法的描述,没有用overload装饰的__init__方法才是真正的__init__方法。我们要在里面实现所有的多态逻辑,首先给__start、__stop、__step赋初值。再判断传入的参数个数,传入一个参数时把值赋给__stop,传入两个参数时把值分别赋给__start和__stop,传入三个值时分别把值赋给__start、__stop、__step,当传入的参数为0个或超过3个时抛出传参个数错误。最后检查__step的值是否为0,为0会无限迭代需要控制。这些逻辑都是根据range函数的特性来设计的。

        接下来就可以定义__iter__和__next__来使对象变成迭代器。根据上面原理图的逻辑,需要判断__step的正负,来决定是正向迭代(逐渐增大)还是逆向迭代(逐渐减小),每次迭代值的变化为__step(步长)。我们可以设计出如下代码:

    def __iter__(self):
        return self

    def __next__(self):
        if self.__step > 0:
            return self.__next(self.__stop, self.__start)
        return self.__next(self.__start, self.__stop)

    def __next(self, big, little):
        if little < big:
            value = self.__start
            self.__start += self.__step
            return value
        raise StopIteration

         现在我们把上下两部分结合起来就可以得到一个类似range生成数字的迭代器了。

from typing import overload


class MyRange:
    @overload
    def __init__(self, stop: int):
        ...

    @overload
    def __init__(self, start: int, stop: int):
        ...

    @overload
    def __init__(self, start: int, stop: int, step: int):
        ...

    def __init__(self, *args: int):
        self.__start, self.__stop, self.__step = 0, 0, 1
        if len(args) == 1:
            self.__stop = args[0]
        elif len(args) == 2:
            self.__start, self.__stop = args
        elif len(args) == 3:
            self.__start, self.__stop, self.__step = args
        else:
            raise TypeError(f'MyRange expected at most 3 arguments, got {len(args)}')
        if self.__step == 0:
            raise ValueError('step must not be 0')

    def __iter__(self):
        return self

    def __next__(self):
        if self.__step > 0:
            return self.__next(self.__stop, self.__start)
        return self.__next(self.__start, self.__stop)

    def __next(self, big, little):
        if little < big:
            value = self.__start
            self.__start += self.__step
            return value
        raise StopIteration


if __name__ == '__main__':
    for i in MyRange(1, 10, 2):
        print(i)

执行结果如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值