欢迎光临散文网 会员登陆 & 注册

【python小技巧4】for循环原理与迭代器的实现

2023-01-03 23:02 作者:小倪同学0v0  | 我要投稿

对于新手,for 循环无非 for i in range(10):  for i in lst: 等,那你知道 for 循环真正的原理吗?

目录

  1. 你需要知道的...

  2. for 循环的工作原理(2种情况)

  3. 如何写一个可迭代对象和迭代器

你需要知道的...

  1. for 循环的语法永远是 for xxx in xxx,for 循环又称遍历循环,本质上一切 for 循环,都是在遍历一个对象,无论是 range 还是 list。

  2. 内置函数 与 类的魔术方法 的关系

    python 里有很多“魔术方法”,它们都有一个共同点:以双下划线开头,以双下划线结尾,如 __str__,__int__,__iter__,__next__ 等。(这也是为什么不推荐新手用 __xxx__ 这种当变量名的原因)

    其中有很多魔术方法可被对应的内置函数调用,如 str(), int(), iter(), next() 等。

  3. 本文还涉及了一个魔术方法 __getitem__

    obj.__getitem__(item)  <==>  obj[item],就是列表的取元素操作

  4. 可迭代对象迭代器

    1. 可迭代对象指实现 __iter__ 方法,一般保存了数据,如 list 等。

    2. 迭代器指实现了 __next__ 方法,一般保存了迭代的状态(如迭代到哪一个),如 list_iterator 等。

    3. 严格定义如上,但有一条规定:迭代器应该也是可迭代的。换句话说:如果一个对象实现了 __next__ 方法,那它也应该实现 __iter__ 方法,大部分就是返回自己。

      可迭代对象 与 迭代器的概念一定要记住,很重要!!!

    4. 如何检测一个对象是否是迭代器或可迭代?使用内置模块 collections(新版建议用collections.abc 模块下的 Iterable 和 Iterator 就可以,具体用法:

        注意:字符串、列表、元组、字典、集合等是可迭代的,但不是迭代器!

假设有一个循环:

以下将在这个循环的基础上讲解

第一种情况:

如果 obj 是可迭代的,即实现了 __iter__ 方法,尝试调用 iter(obj) 得到 obj 的迭代器,假设迭代器是 iterator。

然后不断调用 next(iterator),返回值就是迭代出来的值,直到遇到 StopIteration,停止循环。

这种情况下上面的代码与下面是等价的:

第二种情况:

如果 obj 没有实现 __iter__ 方法,则康康有没有实现 __getitem__ 方法。

如果有,则从 0 开始,依次递增 1 来调用 __getitem__ 方法,返回值就是迭代出来的值,直到遇到 StopIteration,停止调用。

这种情况下上面的代码与下面是等价的:

如果以上两种方法都行不通,那就不能用 for 循环(会报TypeError: 'xxx' object is not iterable)

下面再来讲一讲如何实现可迭代对象和迭代器

实现可迭代对象和迭代器

我们试着模仿 range 函数写一个生成等差数列的可迭代对象

现在它既不是迭代器,也不可迭代

我们试着让它的迭代器就是它自己(实现 __iter__ 方法):

已经可迭代了,接下来让它成为迭代器

我们可以对它用 for 循环了:

但是,这种写法有问题,慢慢看来:

我们要输出一个 [1, 2, 3, 4, 5] 与它自己的笛卡尔积(不知道啥是笛卡尔积的看这里https://baike.baidu.com/item/%E7%AC%9B%E5%8D%A1%E5%B0%94%E4%B9%98%E7%A7%AF/6323173),即

传统写法是

这很简单。但如果用咱们编的这个 Range,问题就出来了,看输出:

问题就出在:因为有 return self 的存在,两个 for 循环共用了一个迭代器(换而言之,这个 Range 对象只能用一次)!让这个 Range 对象既保存数据又保存状态是不行哒!

range 对象的 iter() 原理图
自定义的 Range 对象的 iter() 原理图

所以我们需要这里给出两种解决方案:

(一)自定义专门的迭代器

最本质的问题就是 __iter__ 方法里的 return self,我们再定义一个迭代器:

为了要满足第三条规则,即迭代器应该也是可迭代的,上面也说了,大部分就是返回自己,所以再给 RangeIterator 加上 __iter__ 方法:

这样,Range 对象就可以多次使用了,但 RangeIterator 作为迭代器本来就只能使用一次。

至此,一个迭代器已经实现了。

(二)使用生成器

我们下一章详细讨论生成器,这里不理解没关系(挖坑*1)

我们将 __iter__ 方法变成一个生成器函数:

也能达到效果,且更加简洁,推荐使用这种方法。

至于我们刚才讨论的第二种情况,实际使用中很少使用,我们就不再赘述。

END

这算是给之前的一章填坑了...

参考资料:

https://docs.python.org/3.8/tutorial/classes.html

https://docs.python.org/3.8/library/stdtypes.html

https://baike.baidu.com/item/%E7%AC%9B%E5%8D%A1%E5%B0%94%E4%B9%98%E7%A7%AF/6323173

以上内容如有错误,欢迎指出!


【python小技巧4】for循环原理与迭代器的实现的评论 (共 条)

分享到微博请遵守国家法律