以for ... in ...
为核心的迭代语法是Python的常用语法之一.
iterable, 可迭代;iterator, 迭代器.
它们之间虽关系密切但不是同一个概念.
并不是每个对象都是可迭代的. 对不可迭代的对象应用迭代语法会报错.
class NotIterable(object):
pass
not_iterable = NotIterable()
for i in not_iterable:
pass
# iter(not_iterable) # 也会报相同的错误
输出:
Traceback (most recent call last):
File ... in
iter(not_iterable)
TypeError: 'NotIterable' object is not iterable
那么, 如何判断一个对象是否可迭代呢?
Python的内置函数iter(obj)
负责从可迭代对象生成迭代器. 它的工作流程为:
step 1. 先尝试调用obj
的__iter__
方法, 如果有, 则根据它构建一个iterator
对象并返回:
class MyIterable(object):
def __iter__(self):
print('{}.__iter__ is called'.format(self.__class__.__name__))
return iter(range(10))
iterable = MyIterable()
iterator = iter(iterable)
输出:
MyIterable.__iter__ is called
step 2. 如果obj
没有__iter__
方法, 则尝试调用它的__getitem__
方法, 如果有, 则根据它可以生成一个从位置0开始访问的迭代器并返回.
step3. 如果既没有__iter__
方法, 也没有__getitem__
, 则报错.
也就是说, 某个对象只要有__iter__
或__getitem__
方法, 它就是可迭代的.
迭代器, 可以说是一种协议. 按照Python的风格, 实现某种协议并不需要implements
或extends
相应的接口或抽象类(见前文), 而是只需要实现必需的方法即可. 对于迭代器协议来说, 它的必需方法有两个:__iter__
与__next__
.
示例代码:
class MyIterable(object):
def __init__(self, data):
self.data = list(data)
def __iter__(self):
print('{}.__iter__ is called'.format(self.__class__.__name__))
return MyIterator(self.data)# 返回一个iterator
class MyIterator(object):
def __init__(self, data):
self.data = data
self.__idx = -1
def __iter__(self):
return self # 返回自身
def __next__(self): #Python3
self.__idx += 1
if self.__idx >= len(self.data):
raise StopIteration
return self.data[self.__idx]
def next(self): # Python2
return self.__next__()
iterable = MyIterable(range(5))
iterator = iter(iterable)
for i in iterator:
print(i)
输出:
MyIterable.__iter__ is called
0
1
2
3
4
有以下几点需要注意:
1. Python3需要__next__
方法, 但Python2需要next
方法, 只是命名不同.
2. iterable
与iterator
对象都有__iter__
方法, 但用途不一样. 前者返回一个新构建的iterator
, 后者返回self
(也是一个iterator对象).
3. iterator
是有状态的. 示例中的状态变量为self.__idx
, 用于存储当前的访问位置.
4. 当iterator.__next__
方法抛出StopIteration
信号时会停止迭代.
for... in iterable
其实我们很少会显式地调用iter
方法将可迭代对象变成迭代器, 而是直接在可迭代对象上进行迭代, 例如, 上面的for i in ...
可以改写为:
iterable = MyIterable(range(5))
for i in iterable:
print(i)
输出与之前一致.
可以看出, iterable
对象在被迭代前已经被iter(iterable)
包装成iterator
, 只不过iter
方法是由Python解释器替我们调用的.
iterator
会保存访问状态上面已经说过, iterator
是有状态的, 这儿再通过一个例子更具体的说明它的状态性:
iterator = iter(iterable)
print("In iterator:")
for i in range(2):
print(iterator.next())
iterator2 = iter(iterator)# 并不会创建一个新的iterator, 而是返回旧的iterator, 保持了它的访问状态.
print('In iterator2:')
for i in iterator2:
print(i)
print(iterator is iterator2)
iterator3 = iter(iterable) # 创建一个新的iterator, 从头开始访问
print('In iterator3:')
for i in iterator3:
print(i)
输出为:
MyIterable.__iter__ is called
In iterator:
0
1
In iterator2:
2
3
4
True
MyIterable.__iter__ is called
In iterator3:
0
1
2
3
4
__iter__
方法返回一个迭代器__iter__
与__next__
in Python3/ next
in Python2.7. 前者返回self
, 后者控制迭代输出.