Skip to content

迭代器

迭代器(英文:iterator)是一个非常迷人的东西,也常被认为是 Python 的高级编程技能。

我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合 next() 函数进行迭代使用。

基础概念

在讲迭代之前,先搞清楚这些名词:

  • 循环(loop),指的是在满足条件的情况下,重复执行同一段代码。比如,while 语句。
  • 迭代(iterate),指的是按照某种顺序逐个访问列表中的每一项。比如,for 语句。
  • 递归(recursion),指的是一个函数不断调用自身的行为。比如,以编程方式输出著名的斐波纳契数列。
  • 遍历(traversal),指的是按照一定的规则访问树形结构中的每个节点,而且每个节点都只访问一次。
python
arr = ['a', 'b', 'c', 'd']

print('---while 循环---')
index = 0
while index < len(arr):
    print(arr[index])
    index += 1

print('---for 遍历---')
for item in arr:
    print(item)

print('---for 循环---')
for index in range(len(arr)):
    print(arr[index])

print('---递归---')


def func(arr, index):
    if index >= len(arr):
        return
    print(arr[index])
    index += 1
    func(arr, index)


func(arr, 0)

迭代的话题如果要说起来,会很多,这里介绍比较初级的。

可迭代对象(iterable)

什么是可迭代对象,简单的理解就是可以用作 for 循环上的一些对象就是可迭代对象。常见的可以迭代对象有哪些呢?

列表、元组、字典、集合字符串和 open() 打开的文件

从代码角度来说,对象内部实现了 __iter__() 方法或者实现了 __getitem__() 的方法。

简单理解:可以被 for 语句遍历的对象都是可迭代对象

迭代器(iterator)

迭代器是相对于可迭代对象(iterable)来说的,迭代器是继承了可迭代对象的。它相对于 Iterable 而言,它实现了 __iter__()__next__() 方法。另外,迭代器不会一次性把所有的元素都加载到内存中,只是在需要的时候才返回结果。可以把一个可迭代对象通过一定的方法转变为迭代器。

python
>>> arr = ['Python', 'Java', 'C++'] # 列表是一个可迭代对象  
>>> arr
['Python', 'Java', 'C++']
>>> iter_arr = iter(arr) 
>>> iter_arr  # 由可迭代对象的 iter 方法返回一个迭代器
<list_iterator object at 0x000001563E4B3A60>

生成器(generator)

生成器就是一种特殊的迭代器,可以由关键字 yield 来实现;同时迭代器并不是生成器,因为迭代器并没有生成器的部分功能,如数据传入功能。

总之迭代器和生成器在一定的功能上具有很高的相似性,都能起到节约内存的作用——就这个特点,就值得我们去学习,然后应用到编程中。

img

迭代器

判断可以迭代对象

可以使用 isinstance() 判断一个对象是否是 Iterable 对象:

python
>>> from collections.abc import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance(100, Iterable)
False

iter() 函数与 next() 函数

listtuple 等都是可迭代对象,我们可以通过 iter() 函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用 next() 函数来获取下一条数据。iter() 函数实际上就是调用了可迭代对象的 __iter__ 方法。

python
>>> arr = [11, 22, 33, 44, 55]
>>> arr_iter = iter(arr)
>>> next(arr_iter)
11
>>> next(arr_iter)
22
>>> next(arr_iter)
33
>>> next(arr_iter)
44
>>> next(arr_iter)
55
>>> next(li_iter)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

注意,当我们已经迭代完最后一个数据之后,再次调用 next() 函数会抛出 StopIteration 的异常,来告诉我们所有数据都已迭代完成,不用再执行 next() 函数了。

迭代对象和迭代器区别

  • 可迭代对象 :实现了 __iter__() 这个魔法方法

  • 迭代器:实现了 __next____iter__ 这两个魔法方法

  • __next__ :返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常

  • __iter__ :返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。

自定义迭代器

迭代器是用来帮助我们 记录每次迭代访问到的位置 ,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。

实际上,在使用 next() 函数的时候,调用的就是迭代器对象的 __next__ 方法。所以,我们要想构造一个迭代器,就要实现它的 __next__ 方法。但这还不够,python 要求迭代器本身也是可迭代的,所以我们还要为迭代器实现 __iter__ 方法,而 __iter__ 方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的 __iter__ 方法返回自身即可。

一个实现了 __iter__ 方法和 __next__ 方法的对象,就是迭代器。

listtuplesetdict 对象有 __iter__() 方法,标着他们能够迭代。这些类型都是 Python 中固有的,我们能不能自己写一个对象,让它能够迭代呢?

当然呢!要不然 python 怎么强悍呢。

案例-自定义迭代器

python
"""  
自定义一个 MyRange 类。实现 range 的功能。  
但只接收两个参数,起始值与步长,实现无限增长(for 实现死循环)  
"""


class MyRange:
    """ range """

    def __init__(self, start=0, step=1):
        self.start = start
        self.step = step
        self.value = 0

    def __iter__(self):
        return self

    def __next__(self):
        """ 迭代规则 """
        self.value = self.start
        # 没有达到结束值  
        self.start = self.start + self.step
        return self.value

    def reset(self):
        self.value = 0


my_range = MyRange(5, 2)

for i in my_range:
    print(i)

以上代码的含义,是自己仿写了拥有 range() 的对象,这个对象是可迭代的,但是它与 python 的 range 函数还是有蛮大的区别。

接下来定义一个与 range 函数功能类似的迭代器:

python

class MyRange:
    def __init__(self, start, stop=None, step=1):
        if stop is None:
            self.stop = start
            self.start = 0
        else:
            self.start = start
            self.stop = stop
        
        self.step = step
        self.curr = self.start - self.step

    def __iter__(self):
        return self

    def __next__(self):
        self.curr += self.step
        if self.curr < self.stop:
            return self.curr
        raise StopIteration

代码分析如下:

  • MyRange 的初始化方法 __init__() 就不再讲解了。
  • __iter__() 是类中的核心,它返回了迭代器本身。一个实现了__iter__()方法的对象,即意味着其实可迭代的。
  • 含有 next() 的对象,就是迭代器,并且在这个方法中,在没有元素的时候要发起 StopIteration() 异常。

如果对以上类的调用换一种方式:

python
x = MyRange(0, 7)
print(list(x))
print("x.next()==>", x.next())

print(list(x)) 将对象返回值都装进了列表中并打印出来,这个正常运行了。此时指针已经移动到了迭代对象的最后一个,next() 方法没有检测也不知道是不是要停止了,它还要继续下去,当继续下一个的时候,才发现没有元素了,于是返回了 StopIteration()

for 循环的本质

for item in Iterable 循环的本质就是先通过 iter() 函数获取可迭代对象 Iterable 的迭代器,然后对获取到的迭代器不断调用 next() 方法来获取下一个值并将其赋值给 item,当遇到 StopIteration 的异常后循环结束。

迭代器应用场景

迭代器最核心的功能就是可以通过 next() 函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的数据集合,也就是说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间。

举个例子,比如,数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

现在我们想要通过 for...in... 循环来遍历迭代斐波那契数列中的前 n 个数。那么这个斐波那契数列我们就可以用迭代器来实现,每次迭代都通过数学计算来生成下一个数。

python
class FibonacciIterator:
    def __init__(self, n):
        self.num = 0
        self.n = n
        self.start_1 = 0
        self.start_2 = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.num < self.n:
            self.start_1, self.start_2 = self.start_2, self.start_1 + self.start_2
            self.num += 1
            return self.start_1
        raise StopIteration


print(list(FibonacciIterator(5)))

可迭代对象的转化

除了 for 循环能接收可迭代对象,listtuple 等也能接收。

python
arr = list(FibonacciIterator(15))
print(arr)
tup = tuple(FibonacciIterator(6))
print(tup)