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

相关推荐
杨~friendship14 分钟前
Ubuntu上使用qt和opencv显示图像
linux·开发语言·c++·qt·opencv·ubuntu
街 三 仔18 分钟前
【C语言零基础入门篇 - 3】:格式化输入输出、字符操作和sizeof运算符揭秘
c语言·开发语言
喜欢猪猪33 分钟前
TCP/IP网络编程概念及Java实现TCP/IP通讯Demo
开发语言·php
西农小陈41 分钟前
python-字符排列问题
数据结构·python·算法
测试199842 分钟前
使用Selenium进行网页自动化
自动化测试·软件测试·python·selenium·测试工具·自动化·测试用例
小黄酥43 分钟前
Python学习笔记--模块
笔记·python·学习
UvwxyZ66644 分钟前
python日志记录与命令行交互
开发语言·python
解孔明1 小时前
IDEA2023.1添加java虚拟机启动参数,打开断言
java·开发语言
关关不烦恼1 小时前
【Java数据结构】二叉树
java·开发语言·数据结构
苹果酱05671 小时前
使用 React Testing Library 测试自定义 React Hooks
java·开发语言·spring boot·后端·中间件