前言
前篇:
魔术方法(1):使用__str__与__repr__进行字符串操作
正文
魔术方法(2):使用__iter__与__next__实现可迭代
for循环可能是我们最熟悉的循环操作,但是你是否思考过,for循环内部做了些什么?它怎么知道什么时候停止循环?又是如何保证元素的遍历不会出错?
突然讲原理可能难以理解,先仔细观察下列代码和运行结果,认真分析一下打印的先后情况
一个基于魔术方法实现的平方迭代器
运行结果
在开始讲解之前,我们先补充几个知识点:
专业术语的定义
可迭代对象:实现了__iter__方法,也就是可以使用for循环的对象
迭代器:如果一个可迭代对象里面也实现了__next__方法,那么这就是一个迭代器,也就是可以使用next()函数的对象
生成器:一种特殊的迭代器,自动实现了__iter__和__next__方法,也就是调用包含yield的函数所返回的对象(这种用法后面会在高级语法中讲解)
顺带一提,Python的三大基本序列类型就是list,tuple和range,怎么样,没想到吧,range的位格这么高,这可是官方文档(点击查看)自己说的
--------分割--------
接下来让我们逐步分析上述代码:
当我们对一个对象使用for i in XXX的时候,在这一步就会调用该对象的__iter__方法。因为返回的对象会交给for循环处理,所以通常我们会返回的其实就是对象本身即return self
在接下来每次读取值的时候,就会调用对象的__next__方法来得到下一个值,当返回的是停止信号(即StopIteration异常)时,读取结束,这也是为什么会多打印出一个__next__
也就是说使用__iter__来返回一个东西交给for循环,而__next__来逐步获得下一个值,直到抛出StopIteration
请务必理解上述执行过程,然后让我们用于实践
--------分割--------
假如有一个班级类,里面保存了很多学生的姓名,基本代码如下
一个简单的教室类,用来保存学生姓名
如果我们希望遍历这些学生信息,你也许会使用
for student in classroom.students:
但是我个人来说并不喜欢这样,这并不优雅,而且暴露变量的引用往往意味着危险,外部的改变随时会改变对象内部的状态:
my_student = classroom.students
my_student.pop()
print(classroom.students)
当我将对象classroom的学生列表引用暴露出去,再使用这个引用弹出一个元素,对象内部的学生列表也发生了变化,这正是需要警惕的地方(谁说python没指针.doge)
现在我们来给Classroom类实现一个迭代属性,让我们可以对教室进行for循环!
可迭代的教室类
运行结果
这里有一个取巧的方法,不同于上面所说的“__iter__通常返回对象本身”,这里我们完全可以将遍历委托给students列表来实现,我们也不需要关心__next__的返回内容和跳出判断。