阅读目录
楔子
假如我如今有一个列表l=['a','b','c','d','e'],我想取列表中的内容,有几种方式?javascript
首先,我能够经过索引取值l[0],其次咱们是否是还能够用for循环来取值呀?html
你有没有仔细思考过,用索引取值和for循环取值是有着微妙区别的。java
若是用索引取值,你能够取到任意位置的值,前提是你要知道这个值在什么位置。python
若是用for循环来取值,咱们把每个值都取到,不须要关心每个值的位置,由于只能顺序的取值,并不能跳过任何一个直接去取其余位置的值。面试
但你有没有想过,咱们为何可使用for循环来取值?编程
for循环内部是怎么工做的呢?app
迭代器
python中的for循环
要了解for循环是怎么回事儿,我们仍是要从代码的角度出发。ssh
首先,咱们对一个列表进行for循环。ide
for i in [1,2,3,4]: print(i)
上面这段代码确定是没有问题的,可是咱们换一种状况,来循环一个数字1234试试函数
for i in 1234 print(i) 结果: Traceback (most recent call last): File "test.py", line 4, in <module> for i in 1234: TypeError: 'int' object is not iterable
看,报错了!报了什么错呢?“TypeError: 'int' object is not iterable”,说int类型不是一个iterable,那这个iterable是个啥?
假如你不知道什么是iterable,咱们能够翻翻词典,首先获得一个中文的解释,尽管翻译过来了你可能也不知道,可是不要紧,我会带着你一步一步来分析。
迭代和可迭代协议
什么叫迭代
如今,咱们已经得到了一个新线索,有一个叫作“可迭代的”概念。
首先,咱们从报错来分析,好像之因此1234不能够for循环,是由于它不可迭代。那么若是“可迭代”,就应该能够被for循环了。
这个咱们知道呀,字符串、列表、元组、字典、集合均可以被for循环,说明他们都是可迭代的。
咱们怎么来证实这一点呢?
from collections import Iterable l = [1,2,3,4] t = (1,2,3,4) d = {1:2,3:4} s = {1,2,3,4} print(isinstance(l,Iterable)) print(isinstance(t,Iterable)) print(isinstance(d,Iterable)) print(isinstance(s,Iterable))
结合咱们使用for循环取值的现象,再从字面上理解一下,其实迭代就是咱们刚刚说的,能够将某个数据集内的数据“一个挨着一个的取出来”,就叫作迭代。
可迭代协议
咱们如今是从结果分析缘由,能被for循环的就是“可迭代的”,可是若是正着想,for怎么知道谁是可迭代的呢?
假如咱们本身写了一个数据类型,但愿这个数据类型里的东西也可使用for被一个一个的取出来,那咱们就必须知足for的要求。这个要求就叫作“协议”。
能够被迭代要知足的要求就叫作可迭代协议。可迭代协议的定义很是简单,就是内部实现了__iter__方法。
接下来咱们就来验证一下:
print(dir([1,2])) print(dir((2,3))) print(dir({1:2})) print(dir({1,2}))

['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'] ['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
总结一下咱们如今所知道的:能够被for循环的都是可迭代的,要想可迭代,内部必须有一个__iter__方法。
接着分析,__iter__方法作了什么事情呢?
print([1,2].__iter__()) 结果 <list_iterator object at 0x1024784a8>
执行了list([1,2])的__iter__方法,咱们好像获得了一个list_iterator,如今咱们又获得了一个新名词——iterator。
iterator,这里给咱们标出来了,是一个计算机中的专属名词,叫作迭代器。
迭代器协议
既什么叫“可迭代”以后,又一个历史新难题,什么叫“迭代器”?
虽然咱们不知道什么叫迭代器,可是咱们如今已经有一个迭代器了,这个迭代器是一个列表的迭代器。
咱们来看看这个列表的迭代器比起列表来讲实现了哪些新方法,这样就能揭开迭代器的神秘面纱了吧?
''' dir([1,2].__iter__())是列表迭代器中实现的全部方法,dir([1,2])是列表中实现的全部方法,都是以列表的形式返回给咱们的,为了看的更清楚,咱们分别把他们转换成集合,
而后取差集。 ''' #print(dir([1,2].__iter__())) #print(dir([1,2])) print(set(dir([1,2].__iter__()))-set(dir([1,2]))) 结果: {'__length_hint__', '__next__', '__setstate__'}
咱们看到在列表迭代器中多了三个方法,那么这三个方法都分别作了什么事呢?
iter_l = [1,2,3,4,5,6].__iter__() #获取迭代器中元素的长度 print(iter_l.__length_hint__()) #根据索引值指定从哪里开始迭代 print('*',iter_l.__setstate__(4)) #一个一个的取值 print('**',iter_l.__next__()) print('***',iter_l.__next__())
这三个方法中,能让咱们一个一个取值的神奇方法是谁?
没错!就是__next__
在for循环中,就是在内部调用了__next__方法才能取到一个一个的值。
那接下来咱们就用迭代器的next方法来写一个不依赖for的遍历。
l = [1,2,3,4] l_iter = l.__iter__() item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item)
这是一段会报错的代码,若是咱们一直取next取到迭代器里已经没有元素了,就会抛出一个异常StopIteration,告诉咱们,列表中已经没有有效的元素了。
这个时候,咱们就要使用异常处理机制来把这个异常处理掉。
l = [1,2,3,4] l_iter = l.__iter__() while True: try: item = l_iter.__next__() print(item) except StopIteration: break
那如今咱们就使用while循环实现了本来for循环作的事情,咱们是从谁那儿获取一个一个的值呀?是否是就是l_iter?好了,这个l_iter就是一个迭代器。
迭代器遵循迭代器协议:必须拥有__iter__方法和__next__方法。
还帐:next和iter方法