python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python可迭代对象与迭代器

Python可迭代对象与迭代器的用法及说明

作者:掐死你滴温柔

文章主要介绍了迭代器和可迭代对象的概念、Python中如何判断一个对象是否可迭代、序列为什么可迭代、如何使用iter()函数处理可调用对象、Python中迭代器的接口以及Sentence类的迭代器实现

迭代是数据处理的基石:程序将计算应用于数据序列。如果数据太大,在内存中放不下,则需要采用“惰性”的方式获取数据,即只按需获取和处理数据项的一部分,而不是一次性加载整个数据集。这就是迭代器的作用。

在Python中,所有容器类型都是可迭代对象。Python使用可迭代对象提供的迭代器支持以下操作:

单词序列

首先我们通过下面代码开启可迭代对象之旅。

import re
import reprlib

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


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

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

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

    def __repr__(self):
        # reprlib.repr这个实用函数用于生成大型模型结构的简略字符串表现形式
        return f"Sentence({reprlib.repr(self.text)})"

我们测试一下上面的代码:

s = Sentence(text="静夜思:唐【李白】 床前明月光,疑是地上霜。举头望明月,低头思故乡。")
print(s)   # 输出:Sentence('静夜思:唐【李白】 床前...。举头望明月,低头思故乡。')
for i in s:
    print(i)
print(s[0]) # 静夜思
print(s[-1]) # 低头思故乡
print(list(s))  # 输出:['静夜思', '唐', '李白', '床前明月光', '疑是地上霜', '举头望明月', '低头思故乡']

上面代码中传入一个字符串,创建一个Sentence实例。reprlib.repr函数默认情况下生成的字符串最多有30个字符,所以多余的字符串使用...替代。Sentence实例可以迭代,因为可以迭代,使用Sentence对象可用于构建列表和其他可迭代类型。

Python开发者都知道,序列可以迭代,为什么呢?

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

需要迭代对象x时,Python会自动调用iter(x)。

内置函数iter执行以下操作:

所有Python序列可迭代的原因是,按照定义,序列都实现了__getitem__方法。其实,标准的序列也都实现了__iter__方法。因此不仅实现了特殊方法__iter__的对象被视为可迭代对象,实现了__getitem__方法的对象也被视为可迭代对象。

from collections import abc
class Demo:
    def __getitem__(self, item):
        pass

class Demo1:
    def __iter__(self):
        pass

print(iter(Demo()))  # <iterator object at 0x000001E61560C610>
print(isinstance(Demo1(),abc.Iterable)) #True

值得一提的是,在Python3.10开始,检查对象x是否迭代,最准确的方法是使用iter(x)函数,如果不可迭代,则处理TypeError异常。这比isinstence(x,abc.Iterable)更准确,因为iter(x)函数还会考虑过时的__getitem__方法,而抽象基类Iterable则不考虑。

内置的iter()更常被Python自身使用,我们自己很少用到。iter()函数还有另一种用法,不过鲜为人知。

使用iter处理可调用对象

调用iter()时,传入两个参数可以为函数或任何可迭代对象创建迭代器。对于这种用法,第一个参数必须是一个可迭代对象,重复调用产生值;第二个参数是哨符,即一种标记值,如果可调用对象返回哨符,则迭代器抛出StopIteration,而不产出哨符。

例如下面示例:使用iter函数掷一个6面骰子,直到点数为1.

import random

def d6():
    return random.randint(1, 6)

d6_iter = iter(d6, 1)
print(d6_iter)
for i in d6_iter:
    print(i)
输出:
<callable_iterator object at 0x0000016A13F82C80>
4
3
5
6
2
5
3
2
4

上面的iter函数返回的是一个callable_iterator。这个示例中的for循环可能会运行很长时间,但是绝对不会产生1,因为1是哨符。

可迭代对象和迭代器

由上面内容可知,可迭代对象:使用内置函数iter可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的。序列都可以迭代。实现了__getitem__方法,而且接受从0开始的索引,这种对象也可以迭代。

可迭代对象和迭代器之间的关系是:Python从可迭代对象中获取迭代器。

Python中字符串是可以迭代的。表面上看不出来,但是背后有一个迭代器。

s = 'ABC'
for char in s:
    print(char)

假如没有for语句,我们不得不使用while循环模拟:

s='ABC'
s_iter = iter(s)
while True:
    try:
        print(next(s_iter))
    except StopIteration:
        del s_iter # 释放s_iter的引用,即废弃迭代器对象
        break

StopIteration表明迭代器已耗尽。内置函数iter()在内部自行处理for循环和其他迭代上下文(列表推导式,可迭代对象拆包等)的StopIteration异常。

Python标准的迭代器接口有以下两个方法:

回到上面的Sentence类:

s = Sentence(text="静夜思:唐【李白】 床前明月光,疑是地上霜。举头望明月,低头思故乡。")
it = iter(s)
print(it)  # <iterator object at 0x0000026DBBCD3460>
print(next(it))  # 静夜思
print(next(it))  # 唐
print(list(it))  # ['李白', '床前明月光', '疑是地上霜', '举头望明月', '低头思故乡']

可以清楚的看出iter()是如何构建迭代器的,以及next()是如何使用迭代器的,以及无法重置已经耗尽的迭代器。

为Sentence类实现__iter__方法

下面实现经典迭代器设计模式。虽然不符合Python的习惯做法,后续会重构。但是通过下面代码,可以明确可迭代容器和迭代器之间的关系。

import re
import reprlib

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


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

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

    def __repr__(self):
        # reprlib.repr这个实用函数用于生成大型模型结构的简略字符串表现形式
        return f"Sentence({reprlib.repr(self.text)})"

    def __iter__(self):
        return SentenceIterator(self.words)


class SentenceIterator:
    def __init__(self, words):
        self.words = words
        self.index = 0

    def __next__(self):
        try:
            word = self.words[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return word

    def __iter__(self):
        return self

与前一版相比,这里只多了一个__iter__方法,没有了__getitem__方法,为的是明确表明这个类之所以可以迭代,是因为实现了__iter__方法。根据可迭代协议,__iter__方法实例化并返回一个迭代器(SentenceIterator)。

注意:对于上面的SentenceIterator迭代器示例来说,没必要在类中实现__iter__方法,不过这样做是对的,因为迭代器应该实现__iter__和__next__这两个方法,而且这样做能让迭代器通过issubclass(SentenceIterator,abc.Iterator)测试。

不要把可迭代对象变成迭代器

构建可迭代对象和迭代器时经常会出现错误,原因是混淆了二者。要知道,可迭代对象有个__iter__方法,每次都实例化一个新的迭代器;而迭代器要实现__next__方法,返回单个元素,此外还要实现__iter__方法,返回本身。

因此迭代器是可迭代对象,但是可迭代对象不是迭代器。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文