设计模式之迭代器模式

迭代器模式的定义

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

提供一种方法顺序地访问一组组合对象(一个容器)中的各个元素,而又不需要暴露该对象的内部细节。 ## 迭代器模式的设计思想

迭代器模式也称为迭代模式。迭代器其实就是一个指向容器中当前元素的指针,这个指针可以返回当前所指向的元素,可以移到下一个元素的位置,通过这个指针可以遍历容器中的所有元素。迭代器一般至少有以下两种方法

  • current(): 获取当前所指向的元素
  • next(): 将指针移至下一个元素

这是最基本的两个方法,有了这两个方法,就可以从前往后地遍历各个元素。我们可以增加一些方法,比如实现从后往前遍历。一些更为丰富的迭代器功能如下

  • toBegin(): 将指针移至起始的位置
  • toEnd(): 将指针移至结尾的位置
  • current(): 获取当前所指向的元素
  • next(): 将指针移至下一个元素
  • previous(): 将指针移至上一个元素

迭代器模式的抽象模型

代码框架

class BaseIterator:
    """迭代器"""
    def __init__(self, data):
        self.__data = data
        self.toBegin()

    def toBegin(self):
        """将指针移至起始位置"""
        self.__curIdx = -1

    def toEnd(self):
        """将指针移至结尾位置"""
        self.__curIdx = len(self.__data)

    def next(self):
        """移动至下一个元素"""
        if (self.__curIdx < len(self.__data) - 1):
            self.__curIdx += 1
            return True
        else:
            return False

    def previous(self):
        """移动至上一个元素"""
        if (self.__curIdx > 0):
            self.__curIdx -= 1
            return True
        else:
            return False

    def current(self):
        """获取当前的元素"""
        return self.__data[self.__curIdx] if (self.__curIdx < len(self.__data) and self.__curIdx >= 0) else None

def testBaseIterator():
    print("从前往后遍历:")
    iterator = BaseIterator(range(0, 10))
    while (iterator.next()):
        customer = iterator.current()
        print(customer, end='\t')
    print()
    print("从后往前遍历:")
    iterator.toEnd()
    while (iterator.previous()):
        customer = iterator.current()
        print(customer, end='\t')


if __name__ == '__main__':
    testBaseIterator()

输出结果

从前往后遍历:
0   1   2   3   4   5   6   7   8   9   
从后往前遍历:
9   8   7   6   5   4   3   2   1   0

Python 中的迭代器

Iterable

可以直接作用于 for 循环的对象统称为可迭代对象(Iterable),它们有如下两种类型

  1. 集合数据类型,如 list、tuple、dict、set、str 等
  2. 生成器(Generator),包括 () 语法定义的生成器和带 yield 的 generator 函数

Iterable 对象的代码示例如下

# 方法一:使用 () 定义生成器
gen = (x * x for x in range(10))

# 方法二:使用 yield 定义 generator 函数
def fibonacci(maxNum):
    """斐波那契数列的生成器"""
    a = b = 1
    for i in range(maxNum):
        yield a
        a, b = b, a + b

def testIterable():
    print("方法一,0~9 的平方数:")
    for e in gen:
        print(e, end='\t')
    print()

    print("方法二,斐波那契数列:")
    fib = fibonacci(10)
    for n in fib:
        print(n, end='\t')
    print()
    print("内置容器的 for 循环:")
    arr = [x * x for x in range(10)]
    for e in arr:
        print(e, end='\t')
    print()

    print()
    print(type(gen))
    print(type(fib))
    print(type(arr))

if __name__ == '__main__':
    testIterable()

输出结果

内置容器的 for 循环:
0   1   4   9   16  25  36  49  64  81  

<class 'generator'>
<class 'generator'>
<class 'list'>

关于生成器,在《流畅的 Python》中有这样的描述

Python 2.2(2001年)加入了 yield 关建字。这个关建字用于构建生成器(generator),其作用与迭代器一样。 所有生成器都是迭代器,因为生成器完全实现了迭代器接口。不过,根据《设计模式:可复用面向对象软件的基础》一书的定义,迭代器用于从集合中取出元素;而生成器用于“凭空”生成元素。通过斐波纳契数列能很好地说明二者之间的区别:斐波纳契数列中的数有无穷个,在一个集合中放不下。不过要知道,在 Python 社区中,大多数时候都把迭代器生成器视作同一概念。

Iterator

生成器(Generator)不但可以作用于 for 循环,还可以被 next() 函数不断调用并返回下一个值,直到最后抛出 StopIteration 错误,表示无法继续返回下一个值。

可以被 next() 函数调用并不断返回下一个值的对象称为迭代器(Iterator)

我们可以使用 isinstance() 来判断一个对象是否为 Iterable 对象或 Iterator 对象,示例代码如下

# 方法一:使用 () 定义生成器
gen = (x * x for x in range(10))

# 方法二:使用 yield 定义 generator 函数
def fibonacci(maxNum):
    """斐波那契数列的生成器"""
    a = b = 1
    for i in range(maxNum):
        yield a
        a, b = b, a + b

from collections.abc import Iterable, Iterator
# 引入 Iterable 和 Iterator
def testIsIterator():
    print("是否为 Iterable 对象:")
    print(isinstance([], Iterable))
    print(isinstance({}, Iterable))
    print(isinstance((1, 2, 3), Iterable))
    print(isinstance(set([1, 2, 3]), Iterable))
    print(isinstance("string", Iterable))
    print(isinstance(gen, Iterable))
    print(isinstance(fibonacci(10), Iterable))
    print("是否为 Iterator 对象:")
    print(isinstance([], Iterator))
    print(isinstance({}, Iterator))
    print(isinstance((1, 2, 3), Iterator))
    print(isinstance(set([1, 2, 3]), Iterator))
    print(isinstance("string", Iterator))
    print(isinstance(gen, Iterator))
    print(isinstance(fibonacci(10), Iterator))

if __name__ == '__main__':
    testIsIterator()

输出结果

是否为 Iterable 对象:
True
True
True
True
True
True
True
是否为 Iterator 对象:
False
False
False
False
False
True
True

Iterator 和 Iterable 的区别

Iterator 对象可以被 next() 函数不断调用并返回下一个值,直到最后抛出 StopIteration 错误,表示无法继续返回下一个值。

Iterable 对象不能被 next() 函数调用,可以用 iter() 函数将 Iterable 对象转成 Iterator 对象,示例代码如下

def testNextItem():
    print("将 Iterable 对象转成 Iterator 对象:")
    l = [1, 2, 3]
    iterL = iter(l)
    print(next(iterL))
    print(next(iterL))
    print(next(iterL))

    print("next() 函数遍历迭代器元素:")
    fib = fibonacci(4)
    print(next(fib))
    print(next(fib))
    print(next(fib))
    print(next(fib))
    print(next(fib))

if __name__ == '__main__':
    testNextItem()

输出结果

将 Iterable 对象转成 Iterator 对象:
1
2
3
next() 函数遍历迭代器元素:
1
1
2
3
Traceback (most recent call last):
File "C:/Users/17309/PycharmProjects/DesignPatterns/iterator/iterable.py", line 98, in <module>
    testNextItem()
File "C:/Users/17309/PycharmProjects/DesignPatterns/iterator/iterable.py", line 68, in testNextItem
    print(next(fib))
StopIteration

使自定义的类具有 Iterable 或 Iterator 属性

要使自定义的类具有 Iterable 属性,需要实现 __iter__ 方法。

要使自定义的类具有 Iterator 属性,需要实现 __iter____next__ 方法,示例代码如下

class NumberSequence:
    """生成一个间隔为 step 的数字序列"""
    def __init__(self, init, step, max = 100):
        self.__data = init
        self.__step = step
        self.__max = max

    def __iter__(self):
        return self

    def __next__(self):
        if (self.__data < self.__max):
            tmp = self.__data
            self.__data += self.__step
            return tmp
        else:
            raise StopIteration

def testNumberSequence():
    numSeq = NumberSequence(0, 5, 20)
    print(isinstance(numSeq, Iterable))
    print(isinstance(numSeq, Iterator))
    for n in numSeq:
        print(n, end='\t')

if __name__ == '__main__':
    testNumberSequence()

输出结果

True
True
0   5   10  15  

迭代器的类图

一个迭代器一般对应着一个容器类,而一个容器会包含多个元素,这些元素可能会有不同的子类。

迭代器模式的类图

在实际的项目开发中有可能会遇到一些更复杂的逻辑。例如,具有层级关系的组织架构:一个公司有 A、B、C 三个部门,每个部门有自己的成员,这时要遍历一个公司的所有成员,就有如下的类图关系

具有层级结构的容器的迭代器实现


参考:

1、《人人都懂设计模式》(罗伟富) 2、《流畅的 Python》


设计模式之迭代器模式
http://fanyfull.github.io/2021/06/07/设计模式之迭代器模式/
作者
Fany Full
发布于
2021年6月7日
许可协议