谈谈 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 类。

相关推荐
2601_962885725 分钟前
如何用 Python 做 MACD 金叉全市场选股?
开发语言·python
FfHUCisI7 分钟前
无锁 Reactor 网络模型与 gnet / evio 架构拆解
服务器·开发语言·后端·golang
笨笨饿8 分钟前
#145 _Codex 三层故障详细排查与修复步骤
开发语言·人工智能·stm32·单片机·嵌入式硬件
科技研学社18 分钟前
服装厂激光开袋机与智能模板机怎么选?核心工序设备深度对比与落地实战
数据库·python
心中有你021418 分钟前
C 语言文件操作详解:从写入、追加到读取与定位的完整 实战
c语言·开发语言
小张成长计划..30 分钟前
【Linux】26:线程互斥和同步
java·开发语言
张张123y4 小时前
后端第十四课:Agent 记忆系统(让 Agent 记住上下文和用户,跨会话不健忘)
java·大数据·人工智能·python·agent·记忆
阿里嘎多学长9 小时前
2026-10-20 GitHub 热点项目精选
开发语言·程序员·github·代码托管
happylifetree9 小时前
Python67:核心语法-函数进阶-参数类型(函数作为参数)
python
布吉岛的石头9 小时前
Java 程序员第 50 阶段2:预训练的目标函数:自回归语言建模本质
java·开发语言·人工智能·深度学习·回归·transformer