目录
🗣️引言
👥四、验证range()是可迭代对象还是迭代器%E6%98%AF%E5%8F%AF%E8%BF%AD%E4%BB%A3%E5%AF%B9%E8%B1%A1%E8%BF%98%E6%98%AF%E8%BF%AD%E4%BB%A3%E5%99%A8)
🗣️小结
🗣️引言
假如给了你一个列表 lis = [1,2,3,4],叫你读取列表中的数据,那要如何做?首先我们想到的是可能是索引读法,比如要读列表中的第二个元素可以用 lis[1]来读到,
其次那就是可以用最最常用的for循环来读取数据了,其中for循环的取值方式想必大家都应该也必须知道了,那就是———按顺序依次取值,既不关心取到的每一个值的位置,也不会跳过某一个值去取其他位置上的值。那此时想必你心中会有一个疑问那就是for循环为什么会这样取值,在for循环的内部究竟有哪一股神秘的代码驱使它这样工作呢?看完下面内容想必就有了答案!
带着这个疑问我们就来接触一下迭代器吧
⚛️可迭代的
👥一、for循环针对的数据类型特点
在开始前我们先了解两个英语单词
其次我们再看下面这两段代码
第一段:我们先将一个列表进行for循环,得到以下结果
for i in [2,3,4,5] print(i) 输出结果: 2 3 4 5
第二段:换个思路我们将列表替换为数字2345那么结果会是什么呢?
for i in 2345: print(i)
输出结果:
结果竟然是报错:那我们再看报错内容的翻译:
它说int对象是不可迭代的,那么我们现在应该了解到了能够使用for循环的数据类型应该要是可迭代的,那么究竟哪些数据类型时可迭代的呢,我们应该如何区分呢?
👥二、可迭代的与可迭代协议
👤1、迭代的概念
将一个数据集中所有的数据按照顺序“一个接着一个一次取出”这个过程就叫做迭代
👤2、认识dir内置函数和双下方法
- dir内置函数:
python中的dir()函数dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。简而言之dir可以告诉我们所传入的数据类型(参数)所包含的所有内置使用方法
print(dir(list)) #告诉我列表拥有的所有方法 print(dir([1,2])) #告诉我列表拥有的所有方法 #上面的两种方法虽然实参不同但函数返回值相同
输出结果:
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__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__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__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__'等含有双下划线的方法叫做双下方法
首先我们知道让两个列表相加的方法是:
print([1]+[2]) 输出结果 [1,2]
其实还可以这样写:
print([1].__add__([2])) 输出结果: [1,2]
这两者的联系是: 其实在函数运行时并不认识"+,- ,* , /"号,因为程序只认识代码,所以你使用"+"时号程序内部会自动帮你调用"__add__"这个函数方法。
👤3、 如何证明一个数据类型是可迭代的
要证明一个数据类型是否是可迭代的我们要用到collections.abc模块
其中可迭代的数据类型是:list、dic、str、set、range()、f = open()、enumerate
from collections.abc import Iterable a = 135 c = {} print(isinstance(a,Iterable)) print(isinstance(c,Iterable)) print(isinstance([],Iterable)) print(isinstance((1,2),Iterable)) print(isinstance({1,2,3},Iterable)) print(isinstance(list,Iterable)) 输出结果: False True True True True False
找出这些数据类型的公共内置使用方法发现都有'__iter__',可得出结论:含有'__iter__'内置方法的数据是可迭代的(iterable)。
下面就是用集合的去重性找出这些数据类型的公共内置使用方法:
ret = set(dir([]))&set(dir({}))&set(dir(''))&set(dir(range)) print(ret)
👤4、可迭代协议
Python 迭代协议由__iter__方法与__next__方法构成,若对象具有__iter__方法,称该对象为“可迭代对象(iterable object)”也就是可跌代的。若对象具有__next__方法,称该对象为“迭代器(iterator)”。__iter__方法必须返回一个迭代器对象,__next__方法不断的返回下一元素,或者抛出StopIteration。__next__方法是 Python 迭代协议的核心,__iter__方法是迭代协议的辅助——将可迭代对象转换成迭代器。
在大多数情况下,可迭代对象会自动转换成迭代器,迭代操作本质是由“迭代器”负责——每次迭代输出一个元素,当无元素时,会抛出StopIteration。这种自动转换机制是造成“可迭代对象”与“迭代器“概念模糊的主要原因。 另一造成混淆的原因就是,对象即是可迭代对象,也是迭代器——当对象具有__iter__方法与__next__方法,并且__iter__方法返回自身,一个典型对象就是“文件对象”。
我们可以用上面的dir方法判断数据类型内部是否含有__iter__方法,代码如下:
print(1,'__iter__' in dir(int)) print(2,'__iter__' in dir(bool)) print(3,'__iter__' in dir(list)) print(4,'__iter__' in dir(dict)) print(5,'__iter__' in dir(str)) print(6,'__iter__' in dir(tuple)) print(7,'__iter__' in dir(range(1))) print(8,'__iter__' in dir(enumerate([]))) 输出结果: 1 False 2 False 3 True 4 True 5 True 6 True 7 True 8 True
👤5、拓展点:内置enumerate函数的用法
可能会疑问enumerate怎么用使用方法如下:
enumerate()是python的内置函数 enumerate在字典上是枚举、列举的意思 对于一个可迭代的(iterable)/可遍历的对象(如列表、字符串),enumerate将其组成一个索引序列,利用它可以同时获得索引和值 enumerate多用于在for循环中得到计数 例如对于一个seq,得到: (0, seq[0]), (1, seq[1]), (2, seq[2])
enumerate()返回的是一个enumerate对象
使用方法如下:
for i,k in enumerate( ['as','d','hh']): print(i,k) print(enumerate( ['as','d','hh']))
输出结果:
👤6、总结可迭代的知识点
总结一下我们现在所知道的:可以被for循环的都是可迭代的,要想可迭代,内部必须有一个__iter__方法。
那么内部的__iter__方法做了什么事情呢?
print([].__iter__()) 输出结果: <list_iterator object at 0x000002A79C69F640>
执行了list([ ])的__iter__方法,我们好像得到了一个list_iterator,翻译后就是列表迭代器。
这下就明白了一个可迭代对象通过调用内部的__iter__方法就会变成一个迭代器
⚛️迭代器
👥一、迭代器协议
迭代器协议:
迭代器协议---内部含有__next__和__iter__方法的就是迭代器
从上面我们知道了一个可迭代对象通过调用内部的__iter__方法就会变成一个迭代器
[].__iter__()就是一个列表迭代器
我们来看看这个列表的迭代器比起列表来说实现了哪些新方法,这样就能揭开迭代器的神秘面纱了吧?
''' 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__'}
由上面我们知道,迭代器比原类型多出来了{'__setstate__', '__length_hint__', '__next__'}方法,这就是迭代器的秘密
👥二、迭代器秘密方法的使用
上面我们知道迭代器比原类型多出来了{'__setstate__', '__length_hint__', '__next__'}方法
那么这些方法有什么用呢?
#__length_hint__()打印出的是元素个数 print([5,2,0,'swx'].__iter__().__length_hint__()) 输出结果: 4 #__setstate__:可以指定开始取值的位置 #__next__取关键性的作用 l = [1,2,3] iterator = l.__iter__() print(iterator.__next__()) print(iterator.__next__()) print(iterator.__next__()) 输出结果: 1 2 3
这三个方法中,能让我们一个一个取值的神奇方法是谁?没错!就是__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
👥三、迭代器协议的验证
上面说迭代器协议---内部含有__next__和__iter__方法的就是迭代器
用以下代码验证
class A: def __iter__(self):pass #def __next__(self):pass from collections.abc import Iterable from collections.abc import Iterator print(isinstance(A(),Iterator)) print(isinstance(A(),Iterable)) 输出结果: False True class A: def __iter__(self):pass def __next__(self):pass from collections.abc import Iterable from collections.abc import Iterator print(isinstance(A(),Iterator)) print(isinstance(A(),Iterable)) 输出结果: True True
👥四、验证range()是可迭代对象还是迭代器
from collections.abc import Iterator print('__next__' in dir(range(12))) #查看'__next__'是不是在range()方法执行之后内部是否有__next__ print('__iter__' in dir(range(12))) #查看'__next__'是不是在range()方法执行之后内部是否有__iter__ print(isinstance(range(100000000),Iterator)) #验证range执行之后得到的结果不是一个迭代器 输出结果 False True False
👥五、迭代器的好处
迭代器的好处:
- 从容器类型中一个一个取值,会把所有的值都取到。
- 节省内存空间
迭代器并不会在内存中在占用一大块内存 而是随着循环每次生成一个
每一次next每次给我一个
👥六、迭代器总结
迭代器的概念
迭代器协议---内部含有__next__和__iter__方法的就是迭代器
迭代器协议和可迭代协议
可以被for循环的都是可迭代的
可迭代的内部都有__iter__方法
只要是迭代器 一定可迭代
可迭代的.__iter__()方法就可以的到一个迭代器
迭代器中的.__next__()方法可以一个一个的获取值
for循环实际上就是在使用迭代器
iterator 可迭代对象
直接给你内存地址
#直接给你内存地址 print([].__iter__()) 输出结果: <list_iterator object at 0x00000148A66DF0D0> #不给你判断是否可迭代 #猜测它为一个可迭代对象用于判断一个数据类型是否是可迭代的,然后判断是否能够使用for循环 print(range(10)) 输出结果: range(0, 10)
可迭代对象是与for循环挂钩,而迭代器是与next()函数挂钩的
for i in l: pass ''' 执行for循环拿到的每一个值其实都是在for循环内部执行了 : 迭代器就是:iterator = l.__iter__() 然后再执行:iterator.__next__() 直到报错的时候就自动停止了循环 '''
🗣️小结
1、双下方法:__iter__,__next__很少直接调用的方,一般情况下是通过其他语法触发(for) 2、可以被for循环的都是可迭代的 3、可迭代的内部都有__iter__方法 4、只要是迭代器 一定可迭代 5、可迭代的.__iter__()方法就可以的到一个迭代器 6、迭代器中的.__next__()方法可以一个一个的获取值 7、for循环实际上就是在使用迭代器 迭代器的特点: 1、很方便使用,且只能取所有的数据取一次 2、节省内存空间