0
点赞
收藏
分享

微信扫一扫

设计模式--第十一章 迭代模式

浮游图灵 2022-04-13 阅读 81

第十一章 迭代模式


1. 核心思想

提供一种方法顺序地访问一组聚合对象(一个容器)中的各个元素,而又不需要暴露该对象的内部细节。

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

比如医院的排号系统,通过数字化的方式精确地维护着先来先就诊的秩序。医生不用在乎外面有多少人在等待,更不需要了解每一个人的名字和具体信息。他只要在诊断完一个病人后按一下按钮,排号系统就会自动为他呼叫下一位病人,这样医生就可专注于病情的诊断!这个排号系统就如同程序设计中的迭代模式。

2. UML类图

迭代模式UML类图

3. 框架代码

from abc import ABC, abstractmethod


class Aggregate(ABC):
    @abstractmethod
    def createIterator(self):
        pass

    @abstractmethod
    def getitem(self, key):
        pass


class Iterator(ABC):
    @abstractmethod
    def next(self):
        pass

class AggregateImpl(Aggregate):
    def __init__(self, aggregate: list) -> None:
        super().__init__()
        self._aggregate = aggregate

    def createIterator(self) -> Iterator:
        return AggregateImplIterator(self)

    def getitem(self, key):
        return self._aggregate[key]


class AggregateImplIterator(Iterator):
    def __init__(self, aggregate: Aggregate) -> None:
        self.aggregate = aggregate
        self.__index = 0

    def next(self):
        try:
            ele = self.aggregate.getitem(self.__index)
            self.__index += 1
        except IndexError:
            raise StopIteration()
        return ele


if __name__ == "__main__":
    ai = AggregateImpl([1, 2, 3, 4])
    ai_iter = ai.createIterator()
    print(ai_iter.next())
    print(ai_iter.next())
    print(ai_iter.next())
    print(ai_iter.next())
    print(ai_iter.next())

4. 模型说明

  1. 设计要点,在设计迭代模式时,要注意以下几点:
    • 了解容器的数据结构及可能的层次结构。
    • 根据需要确定迭代器要实现的功能,如next()、previous()、current()、toBegin()、toEnd()中的一个或几个。
  2. 优缺点:
    1. 优点:
      • 迭代器模式将存储数据和遍历数据的职责分离。
      • 简化了聚合数据的访问方式。
      • 可支持多种不同的方式(如顺序和逆序)遍历一个聚合对象。
    2. 缺点:
      • 需要额外增加迭代器的功能实现,增加新的聚合类时,可能需要增加新的迭代器。

5. python中迭代器和生成器

迭代是Python中常用且非常强大的一个功能,它可以用于访问集合、列表、字符串、字典等数据结构的元素。我们经常使用循环和条件语句,我们也清楚哪些是可以迭代访问,但是具体它们之间有什么有什么异同之处?有哪些特点?什么是迭代器、什么是生成器、什么是可迭代对象?

5.1 可迭代对象

可迭代对象

可迭代对象是Python中一个非常庞大的概念,它主要包括如下三类:

  • 迭代器
  • 序列
  • 字典

从上图可以看出不同概念之间的关系,迭代器是可迭代对象的一个子集,而生成器又是迭代器的一个子集,是一种特殊的迭代器。除了迭代器之外,Python中还有序列、字典等可迭代对象。

现在已经直观的了解了可迭代对象与迭代器、生成器之间的关系,那么用Python语言怎么表述它们的区别呢?

  • 可迭代对象需要实现__iter__方法
  • 迭代器不仅要实现__iter__方法,还需要实现__next__方法

在使用层面,可迭代对象可以通过**innot in**访问对象中的元素,举一个例子,

X = set([1,2,3,4,5])
print(X)
print(type(X))
print(1 in X)
print(2 not in X)
for x in X:
    print(x)
    
# 输出
{1, 2, 3, 4, 5}
<class 'set'>
True
False
1
2
3
4
5

前面提到,可迭代对象实现了__iter__方法,但是它没有实现__next__,这也是判定迭代器和其他可迭代对象的关键之处,可以看一下通过next访问上述示例中可迭代对象X会报错,

next(X)

# 输出
TypeError: 'set' object is not an iterator

报的错误是'set' object is not an iterator,它指明了set集合是一个可迭代对象,但不是迭代器,下面就来介绍一下迭代器。

5.2 迭代器

迭代器是可迭代对象的一个子集,它是一个可以记住遍历的位置的对象,它与列表、元组、集合、字符串这些可迭代对象的区别就在于next方法的实现,其他列表、元组、集合、字符串这些可迭代对象可以很简单的转化成迭代器,通过Python内置的iter数能够轻松把可迭代对象转化为迭代器,下面来看一个例子,

X = [1,2,3,4,5]
print(type(X))
Y = iter(X)
print(type(Y))
print(next(Y))
print(next(Y))
print(next(Y))

# 输出
<class 'list'>
<class 'list_iterator'>
1
2
3

从上述示例中我们可以看出两点:

  • 通过iter函数把list转化成了迭代器
  • 可迭代器能够记住遍历位置,能够通过next方法不断从前往后访问

除了Python内置的iter之外,还可以通过Python内置的工具包itertools创建迭代器,其中函数包括,

  • count
  • cycle
  • repeat
  • accumulate
  • chain
  • compress
  • dropwhile
  • islice
  • product
  • permutations
  • combinations

itertools中包含很多用于创建迭代器的实用方法,如果感兴趣可以访问官方文档进行详细了解。

当然,也可以自己通过实现__iter____next__方法来定义迭代器,

class Iterator(object):
    def __init__(self, array):
        self.x = array
        self.index = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index < len(self.x):
            value = self.x[self.index]
            self.index += 1
        else:
            raise StopIteration
        return value
    
it = Iterator([1,2,3,4,5])
print(type(it))
for i in it:
    print(i)

# 输出
<class '__main__.Iterator'>
1
2
3
4
5

5.3 生成器

从文章开头的流程图可以直观的看出,生成器是迭代器的子集,换句话说,生成器一定是迭代器,但是迭代器不全是生成器对象。

提及生成器就不得不提及一个Python中的关键字yiled,在Python中一个函数可以用yiled替代return返回值,这样的话这个函数就变成了一个生成器对象,举个例子对比一下,

def generator(array):
    for i in array:
        return i
    
gen = generator([1,2,3,4,5])
print(type(gen))

# 输出
<class 'int'>

这是我们常见的return返回方式,这样的话generator函数获取的是一个int型对象,下面看一下换成yield关键字,

def generator(array):
    for i in array:
        yield(i)
        
gen = generator([1,2,3,4,5])
print(type(gen))

# 输出
<class 'generator'>

这样的话获取的是一个生成器generator,除了yield之外,在Python3.3之后还加入了yield from获取生成器,允许一个生成器将其部分操作委派给另一个生成器,使得生成器的用法变得更加简洁,yield from后面需要加上可迭代对象,这样可以把可迭代对象变成生成器,当然,这里的可迭代对象不仅包含列表、元组,还包含迭代器、生成器。yield from相对于yield的有几个主要优点:

  • 代码更加简洁
  • 可以用于生成器嵌套
  • 易于异常处理

下面就从简洁代码方面举个例子说明一下,

def generator(array):
    for sub_array in array:
        yield from sub_array

gen = generator([(1,2,3), (4,5,6,7)])

# 输出
1
2
3
4
5
6
7

当我们需要访问多层/多维可迭代对象时,我们就不需要逐层的去用for ... in ...去访问,可以简单的通过yiled from把生成器委派给子生成器,除此之外还可以通过生成器表达式的方法得到生成式,后面会介绍。

print(next(gen))
print(next(gen))

# 输出
1
2

通过上面示例可以看出,生成器可以像迭代器那样使用iternext方法。

读到这里可以会有疑惑,从这个示例看来生成器和迭代器并没有什么区别啊?为什么生成器还可以称得上是Python中的一大亮点?

首先它对比于迭代器在编码方面更加简洁,这是显而易见的,其次生成器运行速度更快,最后一点,也是需要着重说明的一点:节省内存。

也许在一些理论性实验、学术论文阶段可以不考虑这些工程化的问题,但是在公司做项目时,内存和资源占用是无法逃避的问题 。如果我们使用其他可迭代对象处理庞大的数据时,当创建或者返回值时会申请用于存储整个可迭代对象的内存,显然这是非常浪费的,因为有的元素当前我们用不到,也不会去访问,但它却一直占用这内存。这时候就体现了生成器的优点,它不是一次性把所有的结果都返回,而是当我们每读取一次,它会返回一个结果,当我们不读取时,它就是一个生成器表达式,几乎不占用内存。

5.4 生成器表达式

首先来看一个对比示例,

X = [1, 2, 3, 4, 5]
it = [i for i in X]
gen = (i for i in X)
print(type(X))
print(type(it))
print(type(gen))

# 输出
<class 'list'>
<class 'list'>
<class 'generator'>

首先说一下it = [i for i in X],这种用法叫做列表生成式,在很多编程规范中非常推崇的一种替代for循环的方式,仔细看一下代码会发现,it = [i for i in X]gen = (i for i in X)的区别非常小,只是一个用了中括号,一个用了小括号,但是它们的区别缺失非常大的,使用中括号的叫做列表生成式,获得的返回值是一个列表,而使用小括号叫做生成器表达式,获得的返回结果是一个生成器,这也是前面提到的,除了使用yieldyield from两个关键字外还可以使用生成器表达式获得生成器。

6. 应用场景

  1. 集合的内部结构复杂,不想暴露对象的内部细节,只提供精简的访问方式。
  2. 需要提供统一的访问接口,从而对不同的集合使用统一的算法。
  3. 需要为一系列聚合对象提供多种不同的访问方式。
举报

相关推荐

0 条评论