Anders Wang


我所认识的每个人都是榜样,都有值得我去尊敬和学习的地方。


Python中的迭代器与可迭代

很多人在听到迭代器与可迭代这两个名词时往往会搞不清楚,甚至认为他们是一样的,但是实际上他们是不同的概念。

我们先来直观的区分这两者有什么不同。

可迭代 (iterable):如果一个对象具备有__iter__() 或者 __getitem__()其中任何一个魔术方法的话,这个对象就可以称为是可迭代的。其中,__iter__()的作用是可以让for循环遍历,而__getitem__()方法可以让实例对象通过[index]索引的方式去访问实例中的元素。所以,列表List、元组Tuple、字典Dictionary、字符串String等数据类型都是可迭代的。

迭代器 (iterator): 如果一个对象同时有__iter__()__next__()魔术方法的话,这个对象就可以称为是迭代器。__iter__()的作用前面我们也提到过,是可以让for循环遍历。而__next__()方法是让对象可以通过 next(实例对象) 的方式访问下一个元素。列表List、元组Tuple、字典Dictionary、字符串String等数据类型虽然是可迭代的,但都不是迭代器,因为他们都没有next( )方法。

  • 如何判断可迭代(iterable) & 迭代器(iterator)

我们可以借助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__()方法。

最近的文章

用yield关键字创建生成器

Python使用 生成器(generator) 对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果,因此它不会在内存中创建和存储整个序列,这也是生成器的主要好处。 什么…

Python, 技术博文详细阅读
更早的文章

python的短逻辑

python中的与(and)和或(or)都是短路的与或,短路指的是当通过第一个值能判断出结果时,就不再去查看第二个值了。 Python支持布尔类型的数据,布尔类型只有True和False两种值,但是布…

Python, 技术博文详细阅读
comments powered by Disqus