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大致是:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
__author__ = 'Andy'
"""
大话设计模式
设计模式——迭代器模式
迭代器模式(Iterator Pattern):提供方法顺序访问一个聚合对象中各元素,而又不暴露该对象的内部表示.
"""
#迭代器抽象类
class Iterator(object):
def First(self):
pass
def Next(self):
pass
def Isdone(self):
pass
def CurrItem(self):
pass
#聚集抽象类
class Aggregate(object):
def CreateIterator(self):
pass
#具体迭代器类
class ConcreteIterator(Iterator):
def __init__(self, aggregate):
self.aggregate = aggregate
self.curr = 0
def First(self):
return self.aggregate[0]
def Next(self):
ret = None
self.curr += 1
if self.curr < len(self.aggregate):
ret = self.aggregate[self.curr]
return ret
def Isdone(self):
return True if self.curr+1 >= len(self.aggregate) else False
def CurrItem(self):
return self.aggregate[self.curr]
#具体聚集类
class ConcreteAggregate(Aggregate):
def __init__(self):
self.ilist = []
def CreateIterator(self):
return ConcreteIterator(self)
class ConcreteIteratorDesc(Iterator):
def __init__(self, aggregate):
self.aggregate = aggregate
self.curr = len(aggregate)-1
def First(self):
return self.aggregate[-1]
def Next(self):
ret = None
self.curr -= 1
if self.curr >= 0:
ret = self.aggregate[self.curr]
return ret
def Isdone(self):
return True if self.curr-1<0 else False
def CurrItem(self):
return self.aggregate[self.curr]
if __name__=="__main__":
ca = ConcreteAggregate()
ca.ilist.append("大鸟")
ca.ilist.append("小菜")
ca.ilist.append("老外")
ca.ilist.append("小偷")
itor = ConcreteIterator(ca.ilist)
print itor.First()
while not itor.Isdone():
print itor.Next()
print "————倒序————"
itordesc = ConcreteIteratorDesc(ca.ilist)
print itordesc.First()
while not itordesc.Isdone():
print itordesc.Next()
上面类的设计如下图:
值得注意的是,在迭代器模式类图的聚合部分,可以包含有几种不同的具体的聚合类(ConcreteAggregate)。
这样,在迭代器部分,针对每个具体的聚合类,可以允许有一个或者多个具体的迭代器类。这些具体的迭代器类都实现同一个接口 Iterator。其好处可以为类似(但是不同)的聚合类设计/实现出类似(但是不同)的迭代器,便于复用。
协作关系指 ConcreteIterator 跟踪聚合类中的当前对象,并能够计算出待遍历的后继对象。
在创建列表迭代器的遍历对象之前,必须提供待遍历的列表,一旦有了该列表迭代器对象,就可以按照顺序访问该列表的各个元素。currentItem()操作返回列表中的当前元素,first()操作初始化迭代器,使当前元素指向列表的第一个元素。next()操作将当前元素指针向前推进一步,指向下一个元素。isDone()操作检查是否已经越过最后一个元素,也就是完成了这次遍历。
迭代器模式的优点如下。
-
迭代器模式支持以不同的方式遍历同一聚合,复杂的聚合可以用多种方式进行遍历。例如二叉树遍历方法有 4 种:先序遍历、中序遍历、后序遍历和层次遍历。可以将不同的遍历算法封装在不同的迭代器子类中,每个迭代器保持自己的遍历状态,因此可以进行多种不同方式的遍历。
-
当修改某一个遍历算法时不会影响其他的遍历算法。
-
当修改被遍历的聚合结构代码时,如果该聚合结构没有改变,则相应的遍历算法代码也不需要改变。
-
迭代器简化了聚合的接口。有了迭代器的遍历接口,聚合本身就不需要类似的遍历接口了。这样就简化了聚合的接口。
整理自: