alist = range(2)
for i in alist:
print(i)
0 1
而C++代码如下:
using namespace std;
vector<int> myIntVector;
//往容器mylntVector中添加元素的操作,略
for(vector<int>::iterator - myIntVector.begin();
myIntVectorlterator != myIntVector.end();
myIntVectorIterator++)
{ cout<<*myIntVectorIterator<<"";}
两相对比,可以看到C++的代码中,多了一个 vector<int>:: iterator
类型,
它是什么、有什么用、什么时候用、怎么用,都是C++程序员需要理解和掌握的内容,
所以可以说,在“实现遍历容器”这一事情比使用C++要付出更多的精力去学习更多的内容,
这就是 Python把迭代器内建在语言之中的好处。
但是,并非所有的时候都能够隐藏细节,特别是在写一本书向读者讲述其中的机理的时候。
所以在这里,首先需要向大家介绍一下 iter()
函数 iter()
可以输入两个实参,
但为了简化起见,在这里忽略第二个可选参数,只介绍一个参数的形式。
iter()
函数返冋一个迭代器 对象,接受的参数是一个实现了 __iter__()
方法的容器或迭代器
(精确来说,还支持仅有 __getitem__()
方法的容器)。
对于容器而言,__iter__()
方法返回一个迭代器对象,而对迭代器而言,
它的 __iter__()
方法返回其自身,所以如果用一个迭代器对象 it
,
则当以它为参数调用 iter(it)
时,返回的是自身。
it = iter(alist)
it2 = iter(it)
assert id(it) == id(it2)
到时此,就可以跟大家讲一下迭代器协议了。前文已经说过,所谓协议t是一种松散的约定, 并没有相应的接口定义,所以把协议简单归纳如下:
1)实现__iter__()
方法,返回一个迭代器。
2)实现 next()
方法,返回当前的元素,并指向下一个元素的位置,
如果当前位置已无元素,则抛出 StopItcration
异常。
可以通过以下代码验证这个协议:
alist = range(2)
it = alist.__iter__()
next(it)
0
next(it)
1
# next(it) #此代码会报错
alist = range(2)
it = iter(alist)
while True:
try:
print(next(it))
except StopIteration:
break
0 1
可以看到输出跟最初使用 for
循环是一样的,
其实 for
语句 就是对获取容器的迭代器、
调用迭代器的 next()
方法以及对 Stoplteration
进行处理等流程进行封装的语法糖
(类似的语法糖还有 in/not it
语句)。
迭代器最大的好处是定义了统一的访问容器(或集合)的统一接口, 所以程序员可以随 时定义自己的迭代器,只要实现了迭代器协议就可以。 除此之外,迭代器还有惰性求值的特 性,它仅可以在迭代至当前元素时才计算(或读取)该元素的值, 在此之前可以不存在,在此之后可以销毁,也就是说不需要在遍历之前事先准备好整个迭代过程中的所有元素, 所以非常适合遍历无穷个元素的集合(如斐波那契数列)或巨大的事物(如文件)。
class Fib(object):
def __init__(self):
self._a = 0
self._b = 1
def __iter__(self):
return self
def next(self):
self._a,self._b = self._b ,self._a + self._b
return self._a
for i,f in enumerate(Fib()):
print(f)
if i >10:
break
这段代码能够打印斐波那契数列的前10项。 再来看一下传统的使用容器存储整个数列的方案。
def fib(n):
"""返回小于指定的斐波那契数列"""
result = []
a,b =0,1
while b<n:
result.append(b)
a,b = b ,a+b
return result
fib(10)
[1, 1, 2, 3, 5, 8]
与直接使用容器的代码相比,它仅使用两个成员变量,显而易见更省内存,
并在一些应用场景更省CPU计算资源,所以在编写代码中应当多多使用迭代器协议,避免劣化代码。
对于这一观点,不必怀疑,从Python 2.3版本开始,itertools
成为了标准库的一员已经充分印证这个观点。
迭代器与内置函数
itertools
的目标是提供一系列计算快速、内存高效的函数,这些函数可以单独使用,
也可以进行组合,这个模块受到了 Haskell
等函数式编程语言的启发,
所以大量使用 itertools
模块中的函数的代码,看起来有点像函数式编程语言写推荐,
比如 sum(knap(opcrator.mul, vector1,veetor2)
,能够用来运行两个向量的对应元素乘积之和。
itertools
最为人所熟知的版本,应该算是 zip
、 map
、 filter
、 slice
的替代,
izip(izip_longest)
、 imap(startmap)
、 ifilter(iftlterfalse)
、 islice
,
它们与原来的那几个内置函数有一样的功能,只是返回的是迭代器(在Python3中,新的E0数撤底替换掉了旧函数)。
除了对标准函数的替代, itertods
还提供以下几个有用的函数:chain()
用以同时连续地迭代多个序列;
compress()
、 dropwhile()
和 takewhile()
能用以遴选序列元素;
tee()
就像同名的UNIX应用程序,对序列作n次迭代;
而 groupby
的效果类似SQL中相同拼写的关键字所 带的效果。
[k for k, g in groupby ( ' AAAR.BBBCCDAABBB' ) ] —> A B C D A B
[list(q) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
除了这些针对有限元素的迭代帮助函数之外,还有 count()
、 cycle()
、 repeat()
等函数产生无穷序列,
这3个函数就分别可以产生算术递增数列、无限重复实参序列的序列和重复产 生同一个值的序列。
如果以上这些函数就会感到吃惊,那么接下来的4个组合数学的函数就会更加感到惊讶了,如表6-3所示。
下面通过例子来熟悉一下,第一行是代码,第二行是结果。
import itertools
itertools.product('ABCD',repeat = 2)
<itertools.product at 0x7fc8d5638fc0>
itertools.permutations('ABCD',2)
<itertools.permutations at 0x7fc8d5fd7420>
itertools.combinations('ABCD',2)
<itertools.combinations at 0x7fc8d56310d0>
itertools.combinations_with_replacement('ABCD',2)
<itertools.combinations_with_replacement at 0x7fc8d56313f0>
for i in itertools.product('ABC','123',repeat=2):print(''.join(i))
A1A1 A1A2 A1A3 A1B1 A1B2 A1B3 A1C1 A1C2 A1C3 A2A1 A2A2 A2A3 A2B1 A2B2 A2B3 A2C1 A2C2 A2C3 A3A1 A3A2 A3A3 A3B1 A3B2 A3B3 A3C1 A3C2 A3C3 B1A1 B1A2 B1A3 B1B1 B1B2 B1B3 B1C1 B1C2 B1C3 B2A1 B2A2 B2A3 B2B1 B2B2 B2B3 B2C1 B2C2 B2C3 B3A1 B3A2 B3A3 B3B1 B3B2 B3B3 B3C1 B3C2 B3C3 C1A1 C1A2 C1A3 C1B1 C1B2 C1B3 C1C1 C1C2 C1C3 C2A1 C2A2 C2A3 C2B1 C2B2 C2B3 C2C1 C2C2 C2C3 C3A1 C3A2 C3A3 C3B1 C3B2 C3B3 C3C1 C3C2 C3C3