很多人在听到迭代器与可迭代这两个名词时往往会搞不清楚,甚至认为他们是一样的,但是实际上他们是不同的概念。
我们先来直观的区分这两者有什么不同。
可迭代 (iterable):如果一个对象具备有__iter__()
或者 __getitem__()
其中任何一个魔术方法的话,这个对象就可以称为是可迭代的。其中,__iter__()
的作用是可以让for循环遍历,而__getitem__()
方法可以让实例对象通过[index]索引的方式去访问实例中的元素。所以,列表List、元组Tuple、字典Dictionary、字符串String等数据类型都是可迭代的。
迭代器 (iterator): 如果一个对象同时有__iter__()
和__next__()
魔术方法的话,这个对象就可以称为是迭代器。__iter__()
的作用前面我们也提到过,是可以让for循环遍历。而__next__()
方法是让对象可以通过 **next(实例对象) **的方式访问下一个元素。列表List、元组Tuple、字典Dictionary、字符串String等数据类型虽然是可迭代的,但都不是迭代器,因为他们都没有next( )方法。
我们可以借助Python中的**isinstance(object, classinfo)**函数来判断一个对象是否是一个已知类型。如下例子中,通过isinstance( )函数分别判断列表、元组、字典、字符串是不是可迭代或迭代器。
from collections import Iterable
from collections import Iterator
print(f"List is 'Iterable': {isinstance([], Iterable)}")
print(f"Tuple is 'Iterable': {isinstance((), Iterable)}")
print(f"Dict is 'Iterable': {isinstance({}, Iterable)}")
print(f"String is 'Iterable': {isinstance('', Iterable)}")
print("="*25)
print(f"List is 'Iterator': {isinstance([], Iterator)}")
print(f"Tuple is 'Iterator': {isinstance((), Iterator)}")
print(f"Dict is 'Iterator': {isinstance({}, Iterator)}")
print(f"String is 'Iterator': {isinstance('', Iterator)}")
# 输出如下:
# List is 'Iterable': True
# Tuple is 'Iterable': True
# Dict is 'Iterable': True
# String is 'Iterable': True
# =========================
# List is 'Iterator': False
# Tuple is 'Iterator': False
# Dict is 'Iterator': False
# String is 'Iterator': False
通过对定义的分析和比较我们得知:迭代器都是可迭代的(因为迭代器都包含__iter__()函数),但可迭代的不一定是迭代器(因为未必每个可迭代就包含__next__()方法)。
得益于Python的鸭子类型特性,只要我们实现类似具备某一特征的方法,就可以认为它就是什么。所以我们定义了一个类并在类中实现__iter__()和__next__()方法,那么这个类就可以当做是一个迭代器了。
from collections import Iterator
class Data:
def __init__(self, x):
self.x = x
def __iter__(self):
return self
def __next__(self):
if self.x >= 10:
raise StopIteration
else:
self.x += 1
return self.x
data = Data(0)
print(f"data is 'Iterator': {isinstance(data, Iterator)}")
# 输出如下:
# data is 'Iterator': True
如上例子中我们可以看到,最后我们用isinstance()函数判断得到结果为True,证明我们定义的实例对象是一个真正的迭代器了。因为是迭代器,我们就可以用for循环来验证试试。
from collections import Iterator
class Data:
def __init__(self, x):
self.x = x
def __iter__(self):
return self
def __next__(self):
if self.x >= 10:
raise StopIteration
else:
self.x += 1
return self.x
data = Data(0)
for d in data:
print(d)
# 输出如下:
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 10
上述例子中,我们定义的类对象内部,把x的值显示在大于等于10以内,否则就会抛出StopIteration异常错误(当然实际使用时并不会出错,只是中断继续执行。)我们先创建了一个初始值为0的实例对象,最后顺利的用for循环遍历了从1到10的数字,因为内部对大于等于10的限制,所以输出到10的时候就停止了。特别要注意的是,如果你再次单独去执行for循环的话不会有任何输出,因为迭代器默认只运行一次。
除了自己定义__iter__()
和__next__()
魔术方法的外,我们还可以使用Python内置的**iter()**函数来返回一个迭代器,像下面这样。
list_a = [1,2,3,4,5,6]
my_iterator = iter(list_a)
print(f"my_iterator is 'Iterator': {isinstance(my_iterator, Iterator)}")
# 输出如下:
# my_iterator is 'Iterator': True
我们知道,迭代器必须具备两个基本方法__iter__()
和__next__()
,而__next__()
方法是让对象可以通过 **next(实例对象) **的方式访问下一个元素。所以让我们验证下用next()的方式去访问这个我们转换过的迭代器是否能正常运行。
next(my_iterator)
# 输出如下:
# 1
# 2
# 3
# 4
# 5
# 6
通过**next(实例对象) **的方式可以访问出每个元素。但是这里要特别说明的一点是,next()函数只能每次执行一次才输出一次结果,如上从1输出到6,是我们手动执行了6次分别执行出来的,如果在输出6后再次执行的话,它就会报一个StopIteration异常错误。
最后,我们还可以使用Python内置的dir()函数来看看传入参数的属性,方法等信息,比如我们用它来看看之前从list转换成的my_iterator迭代器。
dir(my_iterator)
# 输出如下:
# ['__class__',
# '__delattr__',
# '__dir__',
# '__doc__',
# '__eq__',
# '__format__',
# '__ge__',
# '__getattribute__',
# '__gt__',
# '__hash__',
# '__init__',
# '__init_subclass__',
# '__iter__',
# '__le__',
# '__length_hint__',
# '__lt__',
# '__ne__',
# '__new__',
# '__next__',
# '__reduce__',
# '__reduce_ex__',
# '__repr__',
# '__setattr__',
# '__setstate__',
# '__sizeof__',
# '__str__',
# '__subclasshook__']
可以发现,也是意料之中的,my_iterator迭代器包含了两个基本方法__iter__()
和__next__()
方法。