谈谈 Python 可迭代对象的实现

文章目录

    • 可迭代对象
    • [版本1:可迭代对象的 `getitem()` 实现](#版本1:可迭代对象的 __getitem__() 实现)
    • [版本2:可迭代对象的 `iter()` 实现](#版本2:可迭代对象的 __iter__() 实现)
    • 引入生成器知识
    • [版本3: 可迭代对象的生成器函数实现](#版本3: 可迭代对象的生成器函数实现)
    • [版本4: 可迭代对象的生成器表达式实现](#版本4: 可迭代对象的生成器表达式实现)

Python 的可迭代对象我们是很熟悉的,比如 list 、 str、 tuple、 dict 、 file object,它们都支持进行 for 循环操作,每次返回一个项。

可迭代对象

现在有这样一个类,用于记录平时的事项清单

python 复制代码
class Things:

    def __init__(self, owner):
        self.checklist = []
        self.owner = owner

    def add(self, task_name):
        self.checklist.append(task_name)
        return self

things = Things("simon")
things.add("shopping").add("running").add("sleep")

如果要遍历这个 checklist 清单,你应该会这样实现:

python 复制代码
for item in things.checklist:
    print(item)

output >>>

复制代码
shopping
running
sleep

但如果将上面的语句修改成下面的方式就会报错

python 复制代码
for item in things:
    print(item)
shell 复制代码
TypeError: 'Things' object is not iterable

想要知道为什么抛出这个异常,我们需要先知道关于 for 在官方文档的定义:The for-loop is always used in combination with an iterable object, like a list or a range. ,即 for 只能用于 iterable object(可迭代对象)上。上面的代码示例中 things 并不是可迭代对象,而 things.checklist 是一个列表即可迭代的对象。

而关于可迭代对象的定义是:

An object capable of returning its members one at a time.

...

objects of any classes you define with an __iter__() method or with a __getitem__() method that implements sequence semantics.

即对于常见的 list、tuple之外,那些实现了 __iter__() 或者 __getitem__() 方法的类对象也为可迭代对象。我们可以根据这条定义将 things 变成一个可迭代对象。

版本1:可迭代对象的 __getitem__() 实现

python 复制代码
class Things:

    def __init__(self, owner):
        self.checklist = []
        self.owner = owner

    def add(self, task_name):
        self.checklist.append(task_name)

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

things = Things("simon")
things.add("shopping").add("running").add("sleep")
for item in things:
    print(item)

此时还可以对 things 进行类似数组的操作

shell 复制代码
> things[0]
>> "shopping"

版本2:可迭代对象的 __iter__() 实现

这个版本的实现较为繁琐,根据官方文档的定义,开发者需要定义一个 container object(容器对象) 和 iterator object(迭代器对象):

  1. 对于 container obj 需要实现 __iter__() 方法,该方法返回一个 iterator object
  2. 对于 iterator obj 需要实现 iterator.__iter__() 和 iterator.__next__() 两个方法

这个版本的实现和设计模式中的迭代器模式的实现结构一致,这点会在另一篇 blog 提及

下面我们可以实现 __iter__() 的版本:

python 复制代码
class TaskIterator:
    def __init__(self, checklist):
        self.checklist = checklist
        self.index = 0 

    def __iter__(self):
        return self

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

    def __init__(self, owner):
        self.checklist = []
        self.owner = owner

    def add(self, task_name):
        self.checklist.append(task_name)

    def __iter__(self):
        return TaskIterator(self.checklist)

things = Things("simon")
things.add("shopping").add("running").add("sleep")
for item in things:
    print(item)

可迭代对象不一定是迭代器

这里有一个值得注意的细节,对于 TaskIterator 实例我们还可以用 next()

shell 复制代码
> ti = TaskIterator(["a","b","c"])
> next(ti)
>> "a"

但假如我们对 Things 实例用 next() 就会报错提示不是迭代器

shell 复制代码
> next(things)
>> TypeError: 'Things' object is not an iterator

查阅 next() 函数在官方文档的定义:

Retrieve the next item from the iterator by calling its __next__() method. If default is given, it is returned if the iterator is exhausted, otherwise StopIteration is raised.

该函数服务于 iterator 并调用其 __next__() 方法,这里可以看出:

可迭代的 iterable != 迭代对象 iterator

那为什么 for i in things 时,可迭代对象 Things 实例就可以迭代呢?在这里我们需要知道的是,当使用 for 的时候,其内部实际上调用了 python 的内置函数 iter(),该函数返回一个迭代器对象。

Let me start with a brief description of what a for loop does:

  1. The for loop creates an iterator from the iterable using the built-in iter() function.
  2. It calls the iterator's __next__() method and assigns the return value to the variable in the for loop statement.
  3. The program executes the code within the loop. Then, the loop repeats itself and calls the iterator's __next__() method again. This process keeps repeating.
  4. If the iterator's __next__() method raises a StopIteration, the for loop terminates.

换句话说,当我们执行 for i in things 时,实际上是 iter() 函数创建了 TaskIterator 的实例对象,而该实例对象由于实现了迭代器协议(__next__()),所以可以被迭代.

同样的,如果我们将上面的 things 实例用 iter() 函数包一下,则运行正常:

shell 复制代码
> next(iter(things))
>> "shopping"

引入生成器知识

现在我们短暂的跳转到另一个知识点 生成器 generator, 在 python 中有两种便捷的实现方式:生成器函数和生成器表达式

生成器函数

python 复制代码
def numbers(count):
    for n in range(count):
        yield n

number_iterator = numbers(10)
shell 复制代码
> next(number_iterator)
>> 0
> number_iterator
>> <generator object numbers at 0x00000293D4854AD0>

生成器表达式

我们应该对列表生成式非常熟悉,比如 [i+3 for i in range(10)]。而生成器表达式实际上只是将 []改成 (),不同的是后者属于惰性的方式,它不会一下子将所有的数据都创建而占用内存,这在读取超大数据时非常有用。

shell 复制代码
> [i for i in range(10)]
>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
> (i for i in range(10))
>> <generator object <genexpr> at 0x00000293D48545F0>

为啥要提及这个知识点呢?在 python 文档中关于 generator 有这样一段描述:

Generator Types

Python's generators provide a convenient way to implement the iterator protocol. If a container object's __iter__() method is implemented as a generator, it will automatically return an iterator object (technically, a generator object) supplying the __iter__() and __next__() methods. More information about generators can be found in the documentation for the yield expression.

我们回过头来看 版本2 的实现,Things.__iter__() 的目的是返回可迭代对象:

python 复制代码
class Things:
	...
    def __iter__(self):
        return TaskIterator(self.checklist)

而根据这段引用,我们知道可以用生成器对象替代迭代器对象,据此实现更为简洁的版本。

版本3: 可迭代对象的生成器函数实现

python 复制代码
class Things:

    def __init__(self, owner):
        self.checklist = []
        self.owner = owner

    def add(self, task_name):
        self.checklist.append(task_name)

    def __iter__(self):
        for task in self.checklist:
            yield task

版本4: 可迭代对象的生成器表达式实现

python 复制代码
class Things:

    def __init__(self, owner):
        self.checklist = []
        self.owner = owner

    def add(self, task_name):
        self.checklist.append(task_name)

    def __iter__(self):
        return (task for task in self.checklist)

此时,版本3和版本4不再需要额外实现一个 TaskIterator 类。

相关推荐
沐晓时光10 小时前
C语言入门,内存函数知识讲解
c语言·开发语言
用户9562562542914 小时前
跟做:Firecrawl 单页转 Markdown 再丢进沙箱总结
python·api
威哥爱编程14 小时前
模拟面试智能体开发实战 02:回答等半分钟,谁还愿意练?把服务端和流式输出搭起来
python·agent·harmonyos
威哥爱编程14 小时前
模拟面试智能体开发实战 01:面试挂了都不知道挂在哪?用 Python 给自己造一个随叫随到的 AI 面试官
python·agent·harmonyos
2601_9679801114 小时前
Python行情API量化 02:顺序拉 20 要 2 秒?并发批量把时间压到 0.5 秒
python·api·限流·并发·量化
伞伞悦读15 小时前
【第81期】特征缩放怎么选:标准化、MinMax 与 Robust 的原理、泄漏对照和完整实验
人工智能·python·深度学习·机器学习
flyingonpine15 小时前
PowerShell脚本数字签名实战:自签名证书、代码签名与信任链验证
windows·信任链
新*15 小时前
从一段 WebSocket 代码聊开:bytes 不是 zlib、binaryType 决定一切,15MB 大消息的零拷贝优化
javascript·python·websocket·性能优化·blob·zlib·arraybuffer
David猪大卫16 小时前
【C++修炼】map 和 set 的使用
c语言·开发语言·数据结构·c++·经验分享·笔记·学习
卷无止境17 小时前
mathmodel-figure-templates 以及两个上手案例
后端·python