你所不知道的Python迭代器

你所不知道的Python迭代器

迭代就是循环的意思,也就是对一个集合中的元素进行循环,从而得到每一个元素。对于我们自定义的类,也可以让其支持迭代,这就是本文要介绍的特殊成员方法__iter__的作用。用该成员方法可以自定义一个Python迭代器

自定义可迭代的类

可能有的读者会问,为什么不使用列表呢?列表可以获取列表的长度,然后使用变量i对列表索引进行循环,不照样可以获取集合的所有元素吗,还容易理解。没错,使用列表的代码是容易理解,也很好操作,但这是要付出代价的。列表之所以可以用索引来快速定位其中的任何一个元素,是因为列表是一下子将所有的数据都装载的内存中了,而且是一块连续的内存空间。如果数据量比较小还好说,如果数据量很大的话,会非常消耗内存资源。而迭代就不同,迭代是读取多少元素,就将多少元素装载到内存中,不读取就不装载。这有点像处理XML的两种方式:DOM和SAX。DOM是一下子将所有的XML数据都装载到内存中,所以可以快速定位任何一个元素,但代价是消耗内存,而SAX是顺序读取XML文档,没读到的XML文档内容是不会装载到内存中的,所以SAX比较节省内存,但只能从前向后顺序读取XML文档的内容。

如果在一个类中定义__iter__方法,那么这个类的实例就是一个迭代器。iter__方法需要返回一个迭代器,所以就返回对象本身即可(也就是self)。当对象没迭代一次时,就会调用迭代器中的另外一个特殊成员方法__next。该方法需要返回当前迭代的结果。下面让我们先看一个简单的例子,在这个例子中,通过自定义迭代器对由星号(*)组成的直角三角形的每一行进行迭代,然后通过for循环进行迭代,输出一定行数的直角三角形。

# 可无限迭代直角三角形的行
class RightTriangle:
def __init__(self):
    # 定义一个变量n,表示当前的行数
        self.n = 1
def __next__(self):       
    # 通过字符串的乘法获取直角三角形每一行的字符串,每一行字符串的长度是2 * n - 1
        result = '*' * (2 * self.n - 1)
        # 行数加1
        self.n += 1
        return result
    # 该方法必须返回一个迭代器
    def __iter__(self):
        return self
rt = RightTriangle()
# 对迭代器进行迭代
for e in rt:
     # 限制输出行的长度不能大于20,否则会无限输出行
    if len(e) > 20:
        break;
    print(e)

程序运行结果如下图所示。
在这里插入图片描述

将迭代器转换为列表

尽管迭代器很好用,但仍然不具备某些功能,例如,通过索引获取某个元素,进行分片操作。这些操作都是列表的专利,所以在很多时候,需要将迭代器转换为列表。但有很多迭代器都是无限迭代的,就像上一节中的斐波那契数列的迭代。因此,在将迭代器转换为列表时,需要给迭代器能够迭代的元素限定一个范围,否则内存就会溢出了。如果要让迭代器停止迭代,只需要抛出StopIteration异常即可。通过list函数可以直接将迭代器转换为列表。

下面的代码会将斐波那契数列迭代器通过list函数转换为列表。斐波那契数列迭代器限制了最大迭代值不能超过500。

# 将迭代器转换为列表
class Fibonacci:
    def __init__(self):
        self.a = 0
        self.b = 1
    def __next__(self):
        result = self.a
        self.a, self.b = self.b, self.a + self.b
        # 要想让迭代停止,需要抛出StopIteration异常
        if result > 500: raise StopIteration
        return result
    def __iter__(self):
        return self
 
fibs1 = Fibonacci()
# 将迭代器转换为列表
print(list(fibs1))
fibs2 = Fibonacci()
# 使用for循环对迭代器进行迭代
for fib in fibs2:
    print(fib, end = ' ')

程序运行结果如图下图所示。

在这里插入图片描述
从上面的代码可以看出,尽管在__next__方法中,当result大于500时抛出了StopIteration异常,但这个异常是在迭代的过程中由系统处理的,并不会在程序中抛出,所以如果要将无限迭代改成有限迭代,可以在适当的时候抛出StopIteration异常。

发布了1320 篇原创文章 · 获赞 239 · 访问量 139万+

猜你喜欢

转载自blog.csdn.net/nokiaguy/article/details/103664920