Python学习笔记31:迭代技术

Python学习笔记31:迭代技术

  • 本系列文章的代码都存放在Github项目:python-learning-notes
  • 这一部分内容是《Fluent Python》目前为止最长的篇幅,我也花了大半天时间来阅读,内容的确庞杂,所以在提炼整理上可能会有所疏漏,请多包涵。

迭代技术无疑在Python中占有相当的地位。平时我们在写代码的时候,大多数时间也是话费在for或者foreach之类的循环语句上,而Python更进一步,在语言结构中直接整合了迭代技术,让我们可以更容易地在不同类型间使用类似的简单语法就可以进行迭代操作。

接下来我们通过Python迭代技术的核心概念:可迭代对象、迭代器和生成器来深入理解。

可迭代对象和迭代器

Python学习笔记27:类序列对象中我们简单说明过可迭代对象和迭代器,现在我们从语言设计层面来理解这两个概念,先来看一下UML类图:

image-20210511182009294

相当简单,Iterable抽象类代表可迭代对象,Iterator抽象类代表迭代器。

其中可迭代对象仅有一个抽象方法,返回一个迭代器。而迭代器有两个方法,抽象方法__next__作为主要的迭代逻辑,将依次返回元素,完成迭代。而继承自可迭代对象的__iter__方法被重写,用这样的方式实现:

def __iter__(self):
	return self

这是因为我们在Python中会大量使用for/in或者__init__之类的情况来接收和使用可迭代对象,在进行迭代的时候后解释器会调用可迭代对象的__iter__获取一个迭代器,进行具体迭代工作,而这个迭代器本身自然也可以作为一个可迭代对象来使用,所以才会是以上的这种类结构。

下面我们具体分析一下可迭代对象和迭代器。

可迭代对象

__iter__

上面我们说了,可迭代对象必须要实现__iter__,而我们之前Python学习笔记27:类序列对象在实现过序列协议,只实现了__getitem____len__,但是依然可以迭代。

这是因为“Python偏爱序列”。

实际上是因为Python为了兼容旧的序列协议作出的妥协,对于没有实现__iter__方法的序列,解释器会自动尝试通过__getitem__并使用从零开始的下标来构建一个迭代器。

我们用下面这个例子说明:

import re
import reprlib


class Sentence():
    RE_WORD = re.compile('\w+')

    def __init__(self, text) -> None:
        self.text = text
        self.words = Sentence.RE_WORD.findall(text)

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

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

Sentence是一个简单序列,接收字符串,并分割成单词。

测试一下:

from sentence import Sentence
s = Sentence("Today is a good day!")
for word in s:
    print(word)
sIter = iter(s)
print(sIter)
for word in sIter:
    print(word)
# Today
# is
# a
# good
# day
# <iterator object at 0x000002870BB7B4C0>
# Today
# is
# a
# good
# day

可以看到,虽然Sentence没有实现__iter__方法,但依然可以正常迭代,并且还可以用iter函数获取到迭代器,获取到的迭代器同样可以正常用于迭代。

但需要注意的是,虽然Python通过这种方式兼容了序列,但是在事实上序列和可迭代对象是没有继承关系的,我们可以通过下面的代码进行验证:

from collections import abc
print(isinstance(s,abc.Iterable))
print(issubclass(Sentence,abc.Iterable))
# False
# False
迭代器

Python的迭代器相当简洁,只有两个方法,使用的时候也是同样简单:

sIter = iter(s)
already = []
while True:
    print("{!s}^{!s}".format(already, sIter))
    item = next(sIter)
    already.append(item)
# []^<iterator object at 0x000001ABA4B02F70>
# ['Today']^<iterator object at 0x000001ABA4B02F70>
# ['Today', 'is']^<iterator object at 0x000001ABA4B02F70>
# ['Today', 'is', 'a']^<iterator object at 0x000001ABA4B02F70>
# ['Today', 'is', 'a', 'good']^<iterator object at 0x000001ABA4B02F70>
# ['Today', 'is', 'a', 'good', 'day']^<iterator object at 0x000001ABA4B02F70>
# Traceback (most recent call last):
#   File "D:\workspace\python\python-learning-notes\note31\test.py", line 29, in <module>
#     item = next(sIter)
# StopIteration

这里用^表示当前迭代指针的位置。

我们使用next逐步从迭代器中获取元素,在获取完最后一个元素后,会抛出一个StopIteration异常,如果我们使用的是for/in语句,解释器会自动处理这个异常并退出迭代,如果是上面这样手动迭代,就需要处理这个异常:

sIter = iter(s)
already = []
while True:
    print("{!s}^{!s}".format(already, sIter))
    try:
        item = next(sIter)
    except StopIteration:
        break
    already.append(item)
# []^<iterator object at 0x000001D67CD6B4F0>
# ['Today']^<iterator object at 0x000001D67CD6B4F0>
# ['Today', 'is']^<iterator object at 0x000001D67CD6B4F0>
# ['Today', 'is', 'a']^<iterator object at 0x000001D67CD6B4F0>
# ['Today', 'is', 'a', 'good']^<iterator object at 0x000001D67CD6B4F0>
# ['Today', 'is', 'a', 'good', 'day']^<iterator object at 0x000001D67CD6B4F0>

还有一点我们需要注意,Python的迭代器仅实现了一个__next__方法,很简洁,这是优点。但同时意味着功能单一,比如很多其他语言中的迭代器支持“重置”操作,我们可以随时重置迭代器然后从头重新迭代,但Python不行,当一个迭代器迭代完毕后就不能继续使用了,如果你还需要,只能再次使用iter函数获取一个新的迭代器。

迭代器和可迭代对象其实是一种设计模式:迭代器模式。只不过Python通过深入内置这种设计模式让它和Python融为一体。

同样的,我们可以使用“粗苯”的方式来手动实现一个迭代器模式来说明这其中的机制。

迭代器模式
from ast import Index
import re


class SentenceV2():
    RE_WORD = re.compile('\w+')

    def __init__(self, text) -> None:
        self.text = text
        self.words = SentenceV2.RE_WORD.findall(text)

    def __iter__(self):
        return StenceIterator(self)


class StenceIterator():
    def __init__(self, sentence) -> None:
        self.sentence = sentence
        self.index = 0

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

    def __iter__(self):
        return self

我们实现了经典的迭代器模式,这里的StenceIterator就是一个具体的迭代器。

我们看下测试结果:

from sentence_v2 import SentenceV2,StenceIterator
s = SentenceV2("Today is a good day!")
sIter = iter(s)
already = []
while True:
    print("{!s}^{!s}".format(already, sIter))
    try:
        item = next(sIter)
    except StopIteration:
        break
    already.append(item)
print(issubclass(SentenceV2, abc.Iterable))
print(isinstance(s, abc.Iterable))
print(issubclass(StenceIterator, abc.Iterator))
print(isinstance(sIter, abc.Iterator))
# []^<sentence_v2.StenceIterator object at 0x000002200527E550>
# ['Today']^<sentence_v2.StenceIterator object at 0x000002200527E550>
# ['Today', 'is']^<sentence_v2.StenceIterator object at 0x000002200527E550>
# ['Today', 'is', 'a']^<sentence_v2.StenceIterator object at 0x000002200527E550>
# ['Today', 'is', 'a', 'good']^<sentence_v2.StenceIterator object at 0x000002200527E550>
# ['Today', 'is', 'a', 'good', 'day']^<sentence_v2.StenceIterator object at 0x000002200527E550>
# True
# True
# True
# True

可以正常迭代,而且StenceV2SentenceIterator的类对象和实例都通过了issubclassisinstance函数的类型检查。

这事因为我们在Python学习笔记28:从协议到抽象基类中介绍过的subclasshook

简单地说就是通过subclasshook告诉解释器实现了__iter__方法的类都被视为Iterable的子类,实现了__next____iter__的类都被视为Iterator的子类。

subclasshook如何定义感兴趣的可以阅读Python学习笔记28:从协议到抽象基类

事实上上面的例子只是说明迭代器模式的机制,对于将Sentence改造为纯粹的可迭代对象其实很简单:

import re


class SentenceV3():
    RE_WORD = re.compile('\w+')

    def __init__(self, text) -> None:
        self.text 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值