谈谈 Python 可迭代对象的实现

文章目录

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

Python 的可迭代对象我们是很熟悉的,比如 liststrtupledictfile 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 类。

相关推荐
gywl35 分钟前
openEuler VM虚拟机操作(期末考试)
linux·服务器·网络·windows·http·centos
轻口味39 分钟前
命名空间与模块化概述
开发语言·前端·javascript
某柚啊2 小时前
Windows开启IIS后依然出现http error 503.the service is unavailable
windows·http
晓纪同学2 小时前
QT-简单视觉框架代码
开发语言·qt
威桑2 小时前
Qt SizePolicy详解:minimum 与 minimumExpanding 的区别
开发语言·qt·扩张策略
飞飞-躺着更舒服2 小时前
【QT】实现电子飞行显示器(简易版)
开发语言·qt
明月看潮生2 小时前
青少年编程与数学 02-004 Go语言Web编程 16课题、并发编程
开发语言·青少年编程·并发编程·编程与数学·goweb
明月看潮生2 小时前
青少年编程与数学 02-004 Go语言Web编程 17课题、静态文件
开发语言·青少年编程·编程与数学·goweb
Java Fans2 小时前
C# 中串口读取问题及解决方案
开发语言·c#
盛派网络小助手2 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#