迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。
迭代概念
使用for循环遍历取值的过程叫做迭代,比如:使用for循环遍历列表获取值的过程
for value in [2, 3, 4]:
print(value)
迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历位置的对象。迭代器对象从第一个元素开始访问,直到所有的元素被访问结束。迭代器只能往前不会后退。
如果你的某个对象可以用for循环去遍历出里面的所有的值,那么他就可以作为迭代器。当然你也可以使用collections库里面的
Iterable,使用
isinstance([],Iterable)判断该对象是否是迭代,代码放在下端:
from collections import Iterable
# 输出true为可迭代,false为不可迭代对象
print(isinstance([], Iterable))
容器
在前面学习了python的许多容器类型,如tuple、list、set、dict等。容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in, not in关键字判断元素是否包含在容器中。
尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力。
可迭代对象(iterable)
可迭代对象 Iterable:表示该对象可迭代,其并不是指某种具体数据类型。使用for循环遍历取值的对象叫做可迭代对象, 比如:列表、元组、字典、集合、range、字符串。
也可以自定义可迭代对象,只要是实现了__iter__
方法的类就是可迭代对象。
from collections.abc import Iterable, Iterator
class A(object):
def __init__(self):
self.a = [1, 2, 3]
def __iter__(self):
# 此处返回啥无所谓
return self.a
cls_a = A()
# True
print(isinstance(cls_a, Iterable))
但是对象如果是 Iterable 的,看起来好像也没有特别大的用途,因为你依然无法迭代,实际上 Iterable 仅仅是提供了一种抽象规范接口。
for a in cls_a:
print(a)
# 程序报错,iter()返回了不是迭代器的 'list'
#但如果变为return iter(self.a),就可以了:显式调用列表的__iter__方法返回一个迭代器
TypeError: iter() returned non-iterator of type 'list'
迭代器 Iterator
迭代器 Iterator:其和 Iterable 之间是一个包含与被包含的关系,如果一个对象是迭代器 Iterator,那么这个对象肯定是可迭代 Iterable;但是反过来,如果一个对象是可迭代 Iterable,那么这个对象不一定是迭代器 Iterator,可以通过接口协议看出:
class Iterator(Iterable):
# 迭代具体实现
@abstractmethod
def __next__(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
# 返回自身,因为自身有 __next__ 方法(如果自身没有 __next__,那么返回自身没有意义)
def __iter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
return _check_methods(C, '__iter__', '__next__')
return NotImplemented
- 可以发现:实现了
__next__
和__iter__
方法的类才能称为迭代器,就可以被 for 遍历了。到目前为止这句话是正确的,但是当你读到后面就知道这句话不严谨。 - (其实上面就剧透了,从理论上讲__iter__方法只要返回一个包含__next__方法的对象就行了,不一定要是迭代器;上面的list 内部的
__iter__
方法内部返回了具备__next__
方法的类,或者说调用 iter() 后返回的对象本身就是一个迭代器,当然也可以 for 循环了)
class A(object):
def __init__(self):
self.index = -1
self.a = [1, 2, 3]
# 必须要返回一个实现了 __next__ 方法的对象,否则后面无法 for 遍历
# 因为本类自身实现了 __next__,所以通常都是返回 self 对象即可
def __iter__(self):
return self
def __next__(self):
self.index += 1
if self.index < len(self.a):
return self.a[self.index]
else:
# 抛异常,for 内部会自动捕获,表示迭代完成
raise StopIteration("遍历完了")
cls_a = A()
print(isinstance(cls_a, Iterable)) # True
print(isinstance(cls_a, Iterator)) # True 从这里可以看出来同时具有__iter__和__next__的类,是Iterator
print(isinstance(iter(cls_a), Iterator)) # True 这里加不加iter()都一样,因为这个类里面的iter也是直接返回自身(self)
#另外补充一点这个a和上面类里面的a是不一样的;这里的用i(任意字母都可以)也能
for a in cls_a:
print(a)
# 打印 1 2 3
iter()
是Python中的一个内置函数,它的主要功能是返回一个迭代器对象。语法为:
iter(object[, sentinel])
第一个参数表示的是可迭代的对象或者是一个可以进行调用的对象;第二个参数sentinel表示的是一个实际参数,这里需要注意的地方是,如果第二个参数是实参,那么object必须是一个可调用的对象,这个函数就会一直对object进行调用,直到返回sentinel。如果在没有sentinel实参的情况下,那么object必须是可迭代对象,且必须实现__iter__()方法。
不传sentinel
iter(object)
object必须是集合对象,且支持迭代协议(iteration protocol)或者支持序列协议(sequence protocol).
lst = [1, 2, 3 ,4]
lst_iter = iter(lst)
print(next(lst_iter))
print(next(lst_iter))
在这个示例中,如果在不传递sentinel参数的情况下,函数最后返回的是一个迭代器,使用next函数是为了取出迭代器中具体的值。
传sentinel
iter (object, sentinel)
object必须是一个可调用的对象(如函数)。此时,iter创建了一个迭代器对象,每次调用这个迭代器对象的__next__()方法时,都会调用object。如果__next__的返回值等于sentinel,则抛出StopIteration异常,否则返回下一个值。
from functools import partial
with open('source_id', 'rb') as f:
for block in iter(partial(f.read, 64), b''):
print(len(block))
在这个示例中,partial最后返回的是一个偏函数,它是一个可调用对象,一次读取64字节的数据。在这个for循环中,iter函数就会一直去调用这个偏函数,直到偏函数返回的内容是b'', 这代表文件内容读取到了末尾才会结束调用。
无限迭代器
内置函数 iter()的第二
种用法:
iter(callable, sentinel) -> iterator
它在调用时可以接收两个参数 ,其中第一个参数必须是可调用对象(函数) ,第二个参数必须是哨兵。迭代器调用这个函数,直到返回的值等于哨兵。
我们知道python中的 int()
函数默认总是返回0。因此,将它作为 iter(int,1)
传递将返回一个调用 int()的迭代器,直到返回的值等于1。这种情况从来没有发生,我们得到了一个无限迭代器。
不仅如此,我们还可以构建自己的无限迭代器。
class InfIter:
"""一个用来返回所有的奇数的无限迭代器类"""
def __iter__(self):
self.num = 1
return self
def __next__(self):
num = self.num
self.num += 2
return num
输出无穷无尽。
next()
返回迭代器的下一个项目,要和生成迭代器的 iter() 函数一起使用.
next(iterable[, default])
参数说明:
- iterable – 可迭代对象
- default – 可选,用于设置在没有下一个元素时返回该默认值,如果不设置,又没有下一个元素则会触发 StopIteration 异常
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
while True:
try:
# 获得下一个值:
x = next(it)
print(x)
except StopIteration:
# 遇到StopIteration就退出循环
break
StopIteration
如果你有足够的 next()
语句,或者在 for 循环中使用,则上面的例子将永远进行下去。
为了防止迭代永远进行,我们可以使用 StopIteration
语句。
在 __next__()
方法中,如果迭代完成指定的次数,我们可以添加一个终止条件来引发错误:
class MyNumbers:
def __iter__(self):
self.a = 1
return self
def __next__(self):
if self.a <= 20:
x = self.a
self.a += 1
return x
else:
raise StopIteration #终止迭代
myclass = MyNumbers()
myiter = iter(myclass)
for x in myiter:
print(x)
for .. in .. 本质
for .. in .. 也就是常见的迭代操作了。
如
for value in cls_a:
print(value)
其被 python 编译器编译后,实际上代码是:
# 实际调用了 __iter__ 方法返回自身,包括了 __next__ 方法的对象
cls_a = A()
cls_a = iter(cls_a)
while True:
try:
# 然后调用对象的 __next__ 方法,不断返回元素
value = next(cls_a)
print(value)
# 如果迭代完成,则捕获异常即可
except StopIteration:
break
- 可以看出,任何一个对象如果要能够被 for 遍历,必须要实现
__iter__
和__next__
方法,缺一不可。 - 同样的,这句话在我们已经解读过的知识下是正确的,但是不够严谨,甚至表述有点问题,因为该对象不需要直接实现这两个方法也可以得到一个迭代器,后面会说。
- 严谨说法应该是该对象直接或者间接实现了这两个方法都可以返回迭代器(或者说:__iter__返回的对象实现了__next__就可以正常迭代)
明白了上述流程,那么迭代器对象 A,我们可以采用如下方式进行遍历:
myiter = iter(cls_a)
print(next(myiter))
print(next(myiter))
print(next(myiter))
# 因为遍历完了,故此时会出现错误: StopIteration: 遍历完了
print(next(myiter))
我们再来思考 python 内置对象 list 为啥可以被迭代?
b=list([1,2,3])
print(isinstance(b, Iterable)) # True
print(isinstance(b, Iterator)) # False
可以发现 list 类型是可迭代对象,但是其不是迭代器(即 list 没有 __next__
方法),那为啥 for .. in .. 可以迭代呢?
原因是 list 内部的 __iter__
方法内部返回了具备 __next__
方法的类,或者说调用 iter() 后返回的对象本身就是一个迭代器,当然可以 for 循环了。
b=list([1,2,3])
print(dir(b)) # 可以发现其存在 __iter__ 方法,不存在 __next__
b=iter(b) # 调用 list 内部的 __iter__,返回了具备 __next__ 的对象
print(isinstance(b, Iterable)) # True
print(isinstance(b, Iterator)) # True
print(dir(b)) # 同时具备 __iter__ 和 __next__ 方法
基于上述理解我们可以对 A 类代码进行改造(不需要自己再写一遍next),使其更加简单:
class A(object):
def __init__(self):
self.a = [1, 2, 3]
# 我们内部又调用了 list 对象的 __iter__ 方法,故此时返回的对象是迭代器对象
def __iter__(self):
return iter(self.a)
cls_a = A()
print(isinstance(cls_a, Iterable)) # True
print(isinstance(cls_a, Iterator)) # False
for a in cls_a:
print(a)
# 输出: 1 2 3
下面这个例子告诉我们的就是__iter__返回的对象实现了__next__就可以正常迭代,因为A类里面并没有写__next__方法,但它调用了iter(),同样可以做到“返回的对象实现了__next__”
# 仅仅实现 __iter__
class A(object):
def __init__(self):
self.b = B()
def __iter__(self):
return self.b
# 仅仅实现 __next__
class B(object):
def __init__(self):
self.index = -1
self.a = [1, 2, 3]
def __next__(self):
self.index += 1
if self.index < len(self.a):
return self.a[self.index]
else:
# 内部会自动捕获,表示迭代完成
raise StopIteration("遍历完了")
cls_a = A()
cls_b = B()
print(isinstance(cls_a, Iterable)) # True
print(isinstance(cls_a, Iterator)) # False
print(isinstance(cls_b, Iterable)) # False
print(isinstance(cls_b, Iterator)) # False
print(type(iter(cls_a))) # B 对象
print(isinstance(iter(cls_a), Iterator)) # False
for a in cls_a:
print(a)
# 输出: 1 2 3
除了上述这两种实现,还有其他高级语法糖,可以进一步精简代码。
__getitem()__理解
- 上面说过 for .. in .. 的本质就是调用对象的
__iter__
和__next__
方法,但是有一种更加简单的写法,通过仅仅实现__getitem__
方法就可以让对象实现迭代功能。 - 实际上任何一个类,如果实现了
__getitem__
方法,那么当调用 iter(类实例) 时候会自动具备__iter__
和__next__
方法,从而可迭代了。
通过下面例子可以看出,__getitem__
实际上是属于 iter和
next方法的高级封装,也就是我们常说的语法糖,只不过这个转化是通过编译器完成,内部自动转化,非常方便。
from collections.abc import Iterable, Iterator
class A(object):
def __init__(self):
self.a = [1, 2, 3]
def __getitem__(self, item):
return self.a[item]
cls_a = A()
print(isinstance(cls_a, Iterable)) # False
print(isinstance(cls_a, Iterator)) # False
print(dir(cls_a)) # 仅仅具备 __getitem__ 方法
#for value in cls_a: #在这里实际也可以这么写,cls_a会自动类型转换为iter(cls_a)
# print(value)
#——————————————————————————————————————————————————————————————————————————#
cls_a = iter(cls_a) #这应该就是编译器完成,内部自动转化;__getitem__又被拆成__iter__ 和 __next__ 方法了。
#因为我们在这里的类A中并没有看到__iter__ 和 __next__ 方法
print(dir(cls_a)) # 具备 __iter__ 和 __next__ 方法
print(isinstance(cls_a, Iterable)) # True
print(isinstance(cls_a, Iterator)) # True
for value in cls_a:
print(value)
# 输出: 1 2 3
而且 __getitem__
还可以通过索引直接访问元素,非常方便
print(cls_a[0]) #1
print(cls_a[4]) #IndexError
如果你想该对象具备 list 等对象一样的长度属性,则只需要实现 __len__
方法即可
class A(object):
def __init__(self):
self.a = [1, 2, 3]
def __getitem__(self, item):
return self.a[item]
def __len__(self):
return len(self.a)
cls_a = A()
print(len(cls_a)) # 3