当前位置: 首页 > article >正文

谈谈 Python 可迭代对象的实现

文章目录

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

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

可迭代对象

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

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 清单,你应该会这样实现:

for item in things.checklist:
    print(item)

output >>>

shopping
running
sleep

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

for item in things:
    print(item)
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__() 实现

  • refer 文档关于 __getitem__()的定义
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 进行类似数组的操作

> things[0]
>> "shopping"

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

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

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

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

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

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()

> ti = TaskIterator(["a","b","c"])
> next(ti)
>> "a"

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

> 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(),该函数返回一个迭代器对象。

  • refer the-anatomy-of-a-for-loop

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() 函数包一下,则运行正常:

> next(iter(things))
>> "shopping"

引入生成器知识

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

生成器函数

def numbers(count):
    for n in range(count):
        yield n

number_iterator = numbers(10)
> next(number_iterator)
>> 0
> number_iterator
>> <generator object numbers at 0x00000293D4854AD0>

生成器表达式

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

> [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__() 的目的是返回可迭代对象:

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

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

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

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: 可迭代对象的生成器表达式实现

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


http://www.kler.cn/news/283975.html

相关文章:

  • udp可靠传输中ACK与NACK的选择
  • Memcached stats sizes 命令
  • OS库学习之rename(函数)
  • python数据分析——网络爬虫和API
  • 图灵盾IOS SDK
  • 数据结构之拓扑排序
  • 【王树森】RNN模型与NLP应用(6/9):Text Generation(个人向笔记)
  • 【C#】属性的声明
  • Elasticsearch中修改mapping的字段类型该怎么操作
  • Go语言结构快速说明
  • JAVA后端框架--【Mybatis】
  • 【单片机原理及应用】实验:数字秒表显示器
  • ubuntu录屏解决ubuntu下无法播放MP4格式文件的方法
  • 【栈】| 力扣高频题: 基本计算器二
  • 忘掉 Siri 吧:苹果可能会推出拥有自己AI“个性”的机器人设备|TodayAI
  • linux信号处理机制基础(下)
  • 【 WPF 中常用的 `Effect` 类的介绍、使用示例和适用场景】
  • Qt Creator 配置pcl1.14.1
  • 物理机安装Centos后无法连接网络(网线网络)怎么办?-呕心沥血总结版-超简单
  • CSRF漏洞的预防
  • CMake基本语法大全
  • 2024.08.30
  • JVM面试(一)什么是虚拟机?什么是class文件?
  • ASP.NET Core6.0-wwwroot文件夹无法访问解决方法
  • docker基本使用及常见问题
  • github怎么删除项目
  • 使用dom4j.jar包读取xml内的标签等信息
  • 高级java每日一道面试题-2024年8月30日-基础篇-你对泛型了解多少?
  • 私人诊所|基于SprinBoot+vue的私人诊所管理系统(源码+数据库+文档)
  • STM32——看门狗(独立/窗口)