python 中可迭代的对象、迭代器简介

迭代简介:

本节讲述python的可迭代的对象、迭代器和生成器。迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。

与 Lisp(Paul Graham 最喜欢的语言)不同,Python 没有宏,因此为了抽象出迭代器模式,需要改动语言本身。为此,Python 2.2(2001 年)加入了 yield 关键字。 这个关键字用于构建生成器(generator),其作用与迭代器一样。

所有生成器都是迭代器,因为生成器完全实现了迭代器接口。不过,根据《设计模式:可复用面向对象软件的基础》一书的定义,迭代器用于从集合中取出元素;而生成器用于“凭空”生成元素。通过斐波纳契数列能很好地说明二者之间的区别:斐波纳契数列中的数有无穷个,在一个集合里放不下。不过要知道,在 Python 社区中,大多数时候都把迭代器和生成器视作同一概念。

在 Python 3 中,生成器有广泛的用途。现在,即使是内置的 range() 函数也返回一个类似生成器的对象,而以前则返回完整的列表。如果一定要让 range() 函数返回列表,那么必须明确指明(例如,list(range(100)))。

在 Python 中,所有集合都可以迭代。在 Python 语言内部,迭代器用于支持:
1、for 循环
2、构建和扩展集合类型
3、逐行遍历文本文件
4、列表推导、字典推导和集合推导
5、元组拆包
6、调用函数时,使用 * 拆包实参

示例:

import re
import reprlib


RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __getitem__(self, index):
        return self.words[index]

    def __len__(self):
        return len(self.words)

    def __repr__(self):
        # reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表示形式
        return 'Sentence(%s)' % reprlib.repr(self.text)


if __name__ == '__main__':
    s = Sentence('"The time has come," the Walrus said,')
    print(s)
    for word in s:
        print(word)

    # 因为可以迭代,所以 Sentence 对象可以用于构建列表和其他可迭代的类型。
    print(list(s))

序列可以迭代的原因:iter 函数

解释器需要迭代对象 x 时,会自动调用 iter(x)
内置的 iter 函数有以下作用。
(1) 检查对象是否实现了 __iter__ 方法,如果实现了就调用它,获取一个迭代器。
(2) 如果没有实现 __iter__ 方法,但是实现了 __getitem__ 方法,Python 会创建一个迭代 器,尝试按顺序(从索引 0 开始)获取元素。
(3) 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C object is not iterable”( C 对象不可迭代),其中 C 是目标对象所属的类。

任何Python 序列都可迭代的原因是,它们都实现了 __getitem__ 方法。其实,标准的序 列也都实现了 __iter__ 方法,因此你也应该这么做。之所以对 __getitem__ 方法做特殊处 理,是为了向后兼容,而未来可能不会再这么做。

而鸭子类型(duck typing)的极端形式:不仅要实现特殊的 __iter__ 方 法,还要实现 __getitem__ 方法,而且 __getitem__ 方法的参数是从 0 开始的整数(int), 这样才认为对象是可迭代的。

在白鹅类型(goose-typing)理论中,可迭代对象的定义简单一些,不过没那么灵活:如果 实现了 __iter__ 方法,那么就认为对象是可迭代的。此时,不需要创建子类,也不用注册, 因为 abc.Iterable 类实现了 __subclasshook__ 方法。如下面例子可以说明:
在这里插入图片描述
不过要注意,虽然前面定义的Sentence 类是可以迭代的,但却无法通过issubclass,(Sentence, abc.Iterable) 测试。

从 Python 3.4 开始,检查对象 x 能否迭代,最准确的方法是:调用 iter(x) 函数,如果不可迭代,再处理TypeError 异常。这比使用isinstance(x, abc.Iterable) 更准确,因为iter(x) 函数会考虑到遗留的__getitem__ 方 法,而 abc.Iterable 类则不考虑。

迭代对象之前显式检查对象是否可迭代或许没必要,毕竟尝试迭代不可迭代的对象时, Python 抛出的异常信息很明确:TypeError: 'C' object is not iterable。如果除了抛出 TypeError 异常之外还要做进一步的处理,可以使用 try/except 块,而无需显式检查。如果要保存对象,等以后再迭代,或许可以显式检查,因为这种情况可能需要尽早捕获错误。

可迭代的对象与迭代器的对比

可迭代的对象:

使用 iter 内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__ 方法,那么对象就是可迭代的。序列都可以迭代;实现了 __getitem__ 方法,而且其参 数是从零开始的索引,这种对象也可以迭代。

我们要明确可迭代的对象和迭代器之间的关系:Python 从可迭代的对象中获取迭代器。

下面是一个简单的 for 循环,迭代一个字符串。这里,字符串 ‘ABC’ 是可迭代的对象。背 后是有迭代器的,只不过我们看不到:

在这里插入图片描述
如果没有 for 语句,不得不使用 while 循环模拟,要像下面这样写:
在这里插入图片描述
1、 使用可迭代的对象构建迭代器 it。
2、 不断在迭代器上调用 next 函数,获取下一个字符。
3、 如果没有字符了,迭代器会抛出 StopIteration 异常。
4、 释放对 it 的引用,即废弃迭代器对象。

StopIteration 异常表明迭代器到头了。Python 语言内部会处理 for 循环和其他迭代上下 文(如列表推导、元组拆包,等等)中的 StopIteration 异常。

__next__
	返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常。

__iter__
	返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。

这个接口在 collections.abc.Iterator 抽象基类中制定。这个类定义了 __next__ 抽象方法, 而且继承自 Iterable 类;__iter__ 抽象方法则在 Iterable 类中定义,如图所示:
在这里插入图片描述
Iterator 抽象基类实现 __iter__ 方法的方式是返回实例本身(return self)。这样,在需 要可迭代对象的地方可以使用迭代器。如下示例 是 abc.Iterator 类的源码:

class Iterator(Iterable):
    __slots__ = ()

    @abstractmethod
    def __next__(self):
        'Return the next item from the iterator. When exhausted, raise StopIteration'
        raise StopIteration

    def __iter__(self):
        return self

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterator:
            if (any("__next__" in B.__dict__ for B in C.__mro__) and
                    any("__iter__" in B.__dict__ for B in C.__mro__)):
                return True
            return NotImplemented

在 Python 3 中,Iterator 抽象基类定义的抽象方法是 it.__next__(),而在 Python 2 中是 it.next()。一如既往,我们应该避免直接调用特殊方法,使用 next(it) 即可,这个内置的函数在 Python 2 和 Python 3 中都能使用。

注意, abc.Iterator 抽象基类中 __subclasshook__ 方法的作用,使得白鹅类型可以检测的原因。,检查对象 x 是否为迭代器最好的方式是调用 isinstance(x, abc.Iterator)。得 益于Iterator.__subclasshook__ 方法,即使对象x 所属的类不是Iterator 类的真实子类或虚拟子类,也能这样检查。

下面在来看一下 Sentence 类:构建iter()迭代器和使用next()

import re
import reprlib
from collections import abc


RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __getitem__(self, index):
        return self.words[index]

    def __len__(self):
        return len(self.words)

    def __repr__(self):
        # reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表示形式
        return 'Sentence(%s)' % reprlib.repr(self.text)


if __name__ == '__main__':
    # s = Sentence('"The time has come," the Walrus said,')
    # print(s)
    # for word in s:
    #     print(word)
    #
    # 因为可以迭代,所以 Sentence 对象可以用于构建列表和其他可迭代的类型。
    # print(list(s))
    # print(iter(s))
    # print(isinstance(s, abc.Iterable))
    s3 = Sentence('Pig and Pepper')
    it = iter(s3)
    print(it)
    print(next(it))
    print(next(it))
    print(next(it))
    # print(next(it))
    print(list(it))
    print(list(iter(s3)))
    

运行结果:

在这里插入图片描述
因为迭代器只需 __next____iter__ 两个方法,所以除了调用 next() 方法,以及捕获 StopIteration 异常之外,没有办法检查是否还有遗留的元素。此外,也没有办法“还原” 迭代器。如果想再次迭代,那就要调用 iter(...),传入之前构建迭代器的可迭代对象。 传入迭代器本身没用,因为前面说过 Iterator.__iter__ 方法的实现方式是返回实例本身, 所以传入迭代器无法还原已经耗尽的迭代器。

总结:
迭代器:迭代器是这样的对象:实现了无参数的 __next__ 方法,返回序列中的下一个元素;如 果没有元素了,那么抛出 StopIteration 异常。Python 中的迭代器还实现了 __iter__ 方 法,因此迭代器也可以迭代。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值