可迭代对象
在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)
执行结果如下: