PythonI/O进阶学习笔记_8.python的可迭代对象和迭代器、迭代设计模式

 content:
1.什么是迭代协议
2. 什么是迭代器(Iterator)和可迭代对象(Iterable)
3. 使用迭代器和可迭代对象
4. 创建迭代器和可迭代对象
5. 迭代器设计模式
 
一. 什么是迭代协议
之前我们就提到,python中的魔法函数,相当于其对应的协议,如果实现了某种魔法函数,我们可以认为这个类遵循了某种协议,有了某种特定的特质。
在python中遵循了迭代协议的有两个概念,一个是Iterable(可迭代类型),一个是iterator(迭代器)。
看Iterable和Iterator两个类的所有的魔法函数(遵循的协议)来说,
遵循了__iter__,那么这个类型就是可迭代类型。
而Iterator是实现了__next__和__iter__两个魔法函数。
 
二.什么是迭代器(Iterator)和可迭代对象(Iterable)
1.迭代器:
    是访问集合内元素的一种方式,一般用来遍历。简单来说,一个对象实现了__iter__()和__next__()方法,那么它就是一个迭代器对象。例如我们自己需要实现一个迭代器:
class IterObj:
    def __init__(self):
        self.a = [3, 5, 7, 11, 13, 17, 19]
        self.n = len(self.a)
        self.i = 0
    def __iter__(self):
        return iter(self.a)
    def __next__(self):
        while self.i < self.n:
            v = self.a[self.i]
            self.i += 1
            return v
        else:
            self.i = 0
            raise StopIteration()
2.可迭代对象:
   一个对象(在Python里面一切都是对象)只要实现了只要实现了__iter__()方法,那么用isinstance()函数检查就是Iterable对象。
python中常见的可迭代对象:
- 集合或序列类型(如list、tuple、set、dict、str)
- 文件对象
- 在类中定义了__iter__()方法的对象,可以被认为是 Iterable对象,但自定义的可迭代对象要能在for循环中正确使用,就需要保证__iter__()实现必须是正确的(即可以通过内置iter()函数转成Iterator对象。关于Iterator下文还会说明,这里留下一个坑,只是记住iter()函数是能够将一个可迭代对象转成迭代器对象,然后在for中使用)
- 类中实现了如果只实现__getitem__()的对象可以通过iter()函数转化成迭代器但其本身不是可迭代对象。所以当一个对象能够在for循环中运行,但不一定是Iterable对象。
例如:
class IterObj: 
    def __iter__(self): 
        # 这里简单地返回自身 
        # 但实际情况可能不会这么写 
        # 而是通过内置的可迭代对象来实现
        return self
3.更加深刻的理解迭代器和可迭代对象的概念和区别
    由于迭代器模式的使用太常见了,所以大多数编程语言都给常见的容器类型实现了它,例如 Java 中的 Collection,List、Set、Map等。在 Java 中使用迭代器遍历 List 可这么写:
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
    ArrayList 类通过自身的 iterator() 方法获得一个迭代器 iterator,然后由该迭代器实例来落实遍历过程。
    Python 当然也应用了迭代器模式,但它的实现思路跟上例却不太一样。
    首先,Python 认为遍历容器类型并不一定要用到迭代器,因此设计了可迭代对象。
list = [1,2,3,4]
for i in list:
    print(i,end=" ") # 1 2 3 4
for i in list:
    print(i,end=" ") # 1 2 3 4
    上例中的 list 是可迭代对象(Iterable),但并不是迭代器(虽然在底层实现时用了迭代器的部分思想)。Python 抓住了迭代器模式的本质,即是“迭代”,赋予了它极高的地位。
如此设计的好处显而易见:
(1)写法简便,用意直白;
(2)可重复迭代,避免一次性迭代器的缺陷;
(3)不需要创建迭代器,减少开销。
可迭代对象可看作是广义的迭代器,同时,Python 也设计了普通意义的狭义的迭代器。
list = [1,2,3,4]
it = iter(list)
for i in it:
    print(i,end=" ") # 1 2 3 4
for i in it:
    print(i,end=" ") # 无输出
    上例中的 iter() 方法会将可迭代对象变成一个迭代器。从输出结果可以看出,该迭代器的迭代过程是一次性的。
    由此看来,Python 其实是将“迭代器模式”一拆为二来实现:
    一是可迭代思想,广泛播种于容器类型的对象中,使它们都可迭代;
    二是迭代器,一种特殊的可迭代对象,承担普通意义上的迭代器所特有的迭代任务。
(即在一中说到的python中的迭代协议涉及到的概念)
同时,它还提供了将可迭代对象转化为迭代器的简易方法,如此安排,真是将迭代器模式的效力发挥到了极致。
 
三、使用迭代器和可迭代对象
1.迭代器
1)集合和序列对象是可迭代的但不是迭代器,而文件是迭代器
currPath = os.path.dirname(os.path.abspath(__file__)) 
with open(currPath+'/model.py') as file: 
    print(isinstance(file, Iterator)) # true
2)一个迭代器(Iterator)对象不仅可以在for循环中使用,还可以通过内置函数next()函数进行调用
class IterObj:
    def __init__(self):
        self.a = [3, 5, 7, 11, 13, 17, 19]
        self.n = len(self.a)
        self.i = 0
    def __iter__(self):
        return iter(self.a)
    def __next__(self):
        while self.i < self.n:
            v = self.a[self.i]
            self.i += 1
            return v
        else:
            self.i = 0
            raise StopIteration()
 
it = IterObj() 
next(it) # 3 
next(it) # 5
2.可迭代对象
在二.2中,我们列出了python中有哪些对象或者情况符合可迭代对象,那么我们对这些对象如何应用呢?
1)集合或者序列类型 list、tuple、set、dict、str等, 和文件对象。
对这些可迭代对象,可以直接对其进行遍历、和下标索引。
 
2)在类中定义了__iter__()方法的对象
    在1)中提到的类型,都是实现了__iter__属性的。而且可以直接用iter()函数将其转为迭代器来使用。
但是如果是我们自定义了__iter__属性的类,要十分小心。
class IterObj:
    def __iter__(self):
        return self
it = IterObj()
print(iter(it))
我们使用了iter()函数,这时候将再控制台上打印出以下信息:
Traceback (most recent call last):
  File "/Users/mac/PycharmProjects/iterable_iterator_generator.py", line 71, in <module>
    print(iter(it))
TypeError: iter() returned non-iterator of type 'IterObj'
因为__iter__在是实现的时候,需要返回迭代器对象。它不能被iter()使用,因为它是"非迭代器"。
所以要小心我们自己实现__iter__时,一般借助现有的内置的迭代类型,或者自定义迭代器来返回。
例:
class IterObj: 
    def __init__(self): 
        self.a = [3, 5, 7, 11, 13, 17, 19] 
    def __iter__(self): 
        return iter(self.a)
 
3)实现了__getitem__但是不是可迭代对象的类
可以用iter()将其转化为迭代器。
可以使用for等遍历逻辑。
 
 
四、创建迭代器和可迭代对象
1.创建迭代器
创建迭代器有如下方式:
(1)iter() 方法,将可迭代对象转化成迭代器;
(2)__iter__() 与 __next__() 魔术方法,定义类实现这两个魔术方法;(详情看2 自定义迭代器和可迭代对象)
(3)itertools 模块,使用内置模块生成迭代器;可以创建三类迭代器:无限迭代器、有限迭代器与组合迭代器
(4)其它创建方法,如 zip() 、map() 、enumerate() 等等。
 
2.自定义迭代器和可迭代对象
在一、二中,我们注意到Iterator继承了Iterable。那么两者具体在使用上是如何差别使用的呢?
#INPUT:
from collections.abc import Iterable,Iterator
 
a=list()
c=[1,2,3,4,5]
if isinstance(a,Iterable):
    print("a is iterable")
 
if isinstance(c,Iterator):
    print("c is iterator")
 
c_iterator=iter(c)
if isinstance(c_iterator,Iterator):
    print("c_iterator is iterator")
 
#OUTPUT:
a is iterable
c_iterator is iterator
ps:
iter()的作用
 
例:将之前实现过的company的__getitem__进行迭代器改造。
最开始是将company中的用户列表当成一个可迭代对象进行for循环输出:
####Input:
class Company():
    def __init__(self,employees_list):
        self.employees=employees_list
 
    def __getitem__(self, item): 
        return self.employees[item]
 
if __name__=="__main__":
    user_list=["tangrong","tangrong1","tangrong2"]
    user=Company(user_list)
    for i in user:
        print(i)
 
###output:
tangrong
tangrong1
tangrong2
为什么for循环可以完成?
for循环实际上是因为首先去找了__iter__()函数,发现没有会去创建一个默认的迭代器,然后再去找__getitem__函数。
注意__iter__()是返回了一个迭代器对象。
 
如何将上述代码用iter自定义迭代器?
迭代器不支持切片,只是用来做迭代。在继承iterator实现迭代器。__iter__已经在父类实现,可以不用实现但是必须实现__next__。
 
但是__next__是并不接受索引的,迭代器并不支持切片。所以,我们需要在__init__进行参数传递。在init中模拟下标功能。
注意:虽然是用这样的迭代器来实现看起来这样更麻烦了的下标索引,为了方便理解。实际应用中注意区分2者应用场景。
from collections.abc import  Iterator
 
class Company():
    def __init__(self,employees_list):
        self.employees=employees_list
 
    def __iter__(self):
        #实现了__iter__ Company就已经是一个可迭代类型了
        #返回我们自定义的迭代器类型
        return Myiterator(self.employees)
 
class Myiterator(Iterator):
    def __init__(self,employee_list):
        self.iter_list=employee_list
        self.index=0
 
    def __next__(self):
        #真正返回迭代值的逻辑
        try:
            res=self.iter_list[self.index]
            self.index+=1
        except IndexError:
            raise StopIteration
        return res
 
if __name__=="__main__":
    user_list=["tangrong","tangrong1","tangrong2"]
    user=Company(user_list)
    for i in user:
        print(i)
注意迭代器的设计模式,可迭代对象里面内部不要去维护那个index,而是在迭代器内实现。
 
五.迭代器的设计模式
之前就提到过,不止在python中是有迭代器这一概念的,很多语言都有。
其实以上都是设计模式中的一种,也就是迭代器模式。
如果我们想要进阶,设计模式绝对是我们需要了解的。
 
According to GoF, iterator design pattern intent is:
    Provides a way to access the elements of an aggregate object without exposing its underlying represenation.
    #提供一种访问聚合对象元素的方法,而不会暴露其基础表示。
迭代器模式不仅仅是遍历集合,我们可以根据我们的要求提供不同类型的迭代器。
迭代器设计模式通过集合隐藏实际的遍历实现,客户端程序只使用迭代器方法。
 
在二.3中,简单说明了在 Python中的迭代器模式设计。
在四.2中,举例说明了在自定义迭代器的时候,需要遵循的设计模式和规范。
 
迭代器模式常见和常用的有:内部迭代器、外部迭代器、倒序迭代器等等。
 
迭代器模式的结构图如图所示。
迭代器模式的各组成部分及含义说明如下。
  • Aggregate:聚合接口,其实现子类将创建并且维持一个一种数据类型的聚合体。另外,它还定义了创建相应迭代器对象的接口 createIterator。
  • ConcreteAggregate:封装了一个数据存储结构,实现一个具体的集合,如列表、Java 类型 ArrayList 等。一个聚合对象包含一些其他的对象,目的是将这些对象组合成一个新的整体对象,该对象也叫做容器对象或者聚合对象。另外,该类提供了创建相应迭代器对象的方法 createIterator,该方法返回类型为 ConcreteIterator 的一个对象。
  • Iterator:迭代器定义访问和遍历元素的接口。
  • ConcreateIterator(Controller):具体迭代器实现迭代器接口,对该聚合遍历时跟踪当前位置。
 
将上述模型写成demo大致是:
from collections.abc import  Iterator

class Company():
    def __init__(self,employees_list):
        self.employees=employees_list

    def __iter__(self):
        #实现了__iter__ Company就已经是一个可迭代类型了
        #返回我们自定义的迭代器类型
        return Myiterator(self.employees)

class Myiterator(Iterator):
    def __init__(self,employee_list):
        self.iter_list=employee_list
        self.index=0

    def __next__(self):
        #真正返回迭代值的逻辑
        try:
            res=self.iter_list[self.index]
            self.index+=1
        except IndexError:
            raise StopIteration
        return res

if __name__=="__main__":
    user_list=["tangrong","tangrong1","tangrong2"]
    user=Company(user_list)
    for i in user:
        print(i)
上面类的设计如下图:
 
    值得注意的是,在迭代器模式类图的聚合部分,可以包含有几种不同的具体的聚合类(ConcreteAggregate)。
    这样,在迭代器部分,针对每个具体的聚合类,可以允许有一个或者多个具体的迭代器类。这些具体的迭代器类都实现同一个接口 Iterator。其好处可以为类似(但是不同)的聚合类设计/实现出类似(但是不同)的迭代器,便于复用。
    协作关系指 ConcreteIterator 跟踪聚合类中的当前对象,并能够计算出待遍历的后继对象。
    在创建列表迭代器的遍历对象之前,必须提供待遍历的列表,一旦有了该列表迭代器对象,就可以按照顺序访问该列表的各个元素。currentItem()操作返回列表中的当前元素,first()操作初始化迭代器,使当前元素指向列表的第一个元素。next()操作将当前元素指针向前推进一步,指向下一个元素。isDone()操作检查是否已经越过最后一个元素,也就是完成了这次遍历。
 
迭代器模式的优点如下。
  • 迭代器模式支持以不同的方式遍历同一聚合,复杂的聚合可以用多种方式进行遍历。例如二叉树遍历方法有 4 种:先序遍历、中序遍历、后序遍历和层次遍历。可以将不同的遍历算法封装在不同的迭代器子类中,每个迭代器保持自己的遍历状态,因此可以进行多种不同方式的遍历。
  • 当修改某一个遍历算法时不会影响其他的遍历算法。
  • 当修改被遍历的聚合结构代码时,如果该聚合结构没有改变,则相应的遍历算法代码也不需要改变。
  • 迭代器简化了聚合的接口。有了迭代器的遍历接口,聚合本身就不需要类似的遍历接口了。这样就简化了聚合的接口。
 
 
整理自:

猜你喜欢

转载自www.cnblogs.com/besttr/p/12074559.html