
对于新手,for 循环无非 for i in range(10): for i in lst: 等,那你知道 for 循环真正的原理吗?
目录
你需要知道的...
for 循环的工作原理(2种情况)
如何写一个可迭代对象和迭代器
for 循环的语法永远是 for xxx in xxx,for 循环又称遍历循环,本质上一切 for 循环,都是在遍历一个对象,无论是 range 还是 list。
内置函数 与 类的魔术方法 的关系python 里有很多“魔术方法”,它们都有一个共同点:以双下划线开头,以双下划线结尾,如 __str__,__int__,__iter__,__next__ 等。(这也是为什么不推荐新手用 __xxx__ 这种当变量名的原因)其中有很多魔术方法可被对应的内置函数调用,如 str(), int(), iter(), next() 等。
本文还涉及了一个魔术方法 __getitem__obj.__getitem__(item) <==> obj[item],就是列表的取元素操作
可迭代对象 与 迭代器
可迭代对象指实现 __iter__ 方法,一般保存了数据,如 list 等。
迭代器指实现了 __next__ 方法,一般保存了迭代的状态(如迭代到哪一个),如 list_iterator 等。
严格定义如上,但有一条规定:迭代器应该也是可迭代的。换句话说:如果一个对象实现了 __next__ 方法,那它也应该实现 __iter__ 方法,大部分就是返回自己。可迭代对象 与 迭代器的概念一定要记住,很重要!!!
如何检测一个对象是否是迭代器或可迭代?使用内置模块 collections(新版建议用collections.abc 模块) 下的 Iterable 和 Iterator 就可以,具体用法:
# 不推荐使用 from collections import Iterable, Iterator
from collections.abc import Iterable, Iterator
isinstance(obj, Iterable)
isinstance(obj, Iterator) 注意:字符串、列表、元组、字典、集合等是可迭代的,但不是迭代器!
假设有一个循环:
for i in obj:
print(i) 以下将在这个循环的基础上讲解
如果 obj 是可迭代的,即实现了 __iter__ 方法,尝试调用 iter(obj) 得到 obj 的迭代器,假设迭代器是 iterator。
然后不断调用 next(iterator),返回值就是迭代出来的值,直到遇到 StopIteration,停止循环。
这种情况下上面的代码与下面是等价的:
iterator = iter(obj)
while True:
try:
item = next(iterator)
print(item)
except StopIteration:
break 第二种情况:
如果 obj 没有实现 __iter__ 方法,则康康有没有实现 __getitem__ 方法。
如果有,则从 0 开始,依次递增 1 来调用 __getitem__ 方法,返回值就是迭代出来的值,直到遇到 StopIteration,停止调用。
这种情况下上面的代码与下面是等价的:
index = 0
while True:
try:
item = obj[index]
print(item)
index += 1
except StopIteration:
break 如果以上两种方法都行不通,那就不能用 for 循环(会报TypeError: 'xxx' object is not iterable)
下面再来讲一讲如何实现可迭代对象和迭代器
我们试着模仿 range 函数写一个生成等差数列的可迭代对象
class Range:
def __init__(self, start, end, step=1):
assert step &gt; 0 # 简单起见,我们只处理步长 &gt; 0 的情况
self.start = start
self.end = end
self.step = step 现在它既不是迭代器,也不可迭代
我们试着让它的迭代器就是它自己(实现 __iter__ 方法):
class Range:
def __init__(self, start, end, step=1):
assert step &gt; 0 # 简单起见,我们只处理步长 &gt; 0 的情况
self.start = start
self.end = end
self.step = step
def __iter__(self):
return self 已经可迭代了,接下来让它成为迭代器
class Range:
def __init__(self, start, end, step=1):
assert step &gt; 0 # 简单起见,我们只处理步长 &gt; 0 的情况
self.start = start
self.end = end
self.step = step
self.num = start # 保存迭代到哪一个数了,刚开始就是初始值
def __iter__(self):
return self
def __next__(self):
if self.num &lt; self.end: # 还可以继续迭代
output = self.num
self.num += self.step # 增长步长
return output # 返回值
else: # 迭代到头了就触发 StopIteration
raise StopIteration 我们可以对它用 for 循环了:
r = Range(2, 11, 2)
for i in r:
print(i, end=&#39; &#39;)
# 输出:2 4 6 8 10 但是,这种写法有问题,慢慢看来:
我们要输出一个 [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),即
(1, 1) (1, 2) (1, 3) (1, 4) (1, 5)
(2, 1) (2, 2) (2, 3) (2, 4) (2, 5)
(3, 1) (3, 2) (3, 3) (3, 4) (3, 5)
(4, 1) (4, 2) (4, 3) (4, 4) (4, 5)
(5, 1) (5, 2) (5, 3) (5, 4) (5, 5) 传统写法是
r = range(1, 6) #先定义一个 range 对象
for i in r:
for j in r: # 双重循环
print((i, j), end=&#39; &#39;) # end=&#39; &#39; 是不换行的意思
print() # 只打印一个换行 这很简单。但如果用咱们编的这个 Range,问题就出来了,看输出:
(1, 2) (1, 3) (1, 4) (1, 5) 问题就出在:因为有 return self 的存在,两个 for 循环共用了一个迭代器(换而言之,这个 Range 对象只能用一次)!让这个 Range 对象既保存数据又保存状态是不行哒!

range 对象的 iter() 原理图

自定义的 Range 对象的 iter() 原理图
所以我们需要这里给出两种解决方案:
最本质的问题就是 __iter__ 方法里的 return self,我们再定义一个迭代器:
class Range:
def __init__(self, start, end, step=1):
assert step &gt; 0 # 简单起见,我们只处理步长 &gt; 0 的情况
self.start = start
self.end = end
self.step = step
def __iter__(self):
return RangeIterator(self.start, self.end, self.step) # 返回迭代器
class RangeIterator:
def __init__(self, num, end, step):
self.num = num
self.end = end
self.step = step
def __next__(self):
if self.num &lt; self.end: # 还可以继续迭代
output = self.num
self.num += self.step # 增长步长
return output # 返回值
else: # 迭代到头了就触发 StopIteration
raise StopIteration 为了要满足第三条规则,即迭代器应该也是可迭代的,上面也说了,大部分就是返回自己,所以再给 RangeIterator 加上 __iter__ 方法:
class Range:
def __init__(self, start, end, step=1):
assert step &gt; 0 # 简单起见,我们只处理步长 &gt; 0 的情况
self.start = start
self.end = end
self.step = step
def __iter__(self):
return RangeIterator(self.start, self.end, self.step) # 返回迭代器
class RangeIterator:
def __init__(self, num, end, step):
self.num = num
self.end = end
self.step = step
def __next__(self):
if self.num &lt; self.end: # 还可以继续迭代
output = self.num
self.num += self.step # 增长步长
return output # 返回值
else: # 迭代到头了就触发 StopIteration
raise StopIteration
def __iter__(self):
return self 这样,Range 对象就可以多次使用了,但 RangeIterator 作为迭代器本来就只能使用一次。
至此,一个迭代器已经实现了。
我们下一章详细讨论生成器,这里不理解没关系(挖坑*1)
我们将 __iter__ 方法变成一个生成器函数:
class Range:
def __init__(self, start, end, step=1):
assert step &gt; 0 # 简单起见,我们只处理步长 &gt; 0 的情况
self.start = start
self.end = end
self.step = step
def __iter__(self):
n = self.start
while n &lt; self.end:
yield n
n += self.step 也能达到效果,且更加简洁,推荐使用这种方法。
至于我们刚才讨论的第二种情况,实际使用中很少使用,我们就不再赘述。
这算是给之前的一章填坑了...
参考资料:
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