可迭代(Interable),迭代器(Iterator),生成器(generator)的手记(11月26日再次修改)
2020年12月26日 0点22分 更新基础信息
Python基础教程:书P158页
实现了方法__iter__的对象是可迭代的,而实现了方法__next__的对象是迭代器。
这两个方法本质上面没有任何的联系。
今天既然看到这里了,就做个笔记。这个玩意已经花过我很多时间,其实一开始学还好没去纠结它,要不然死的惨兮兮了,网上资料一大堆,反正我是没找到能看懂的,
如果你有机会看到我写的,还是没看懂,请邮件通知我,哪里没看懂,我再写仔细点,至少我觉的,这一块知识,我是可以完全拿下了。
可迭代对象只要有__iter__属性的都可以称呼可迭代(Interable)。
先说明,可迭代对象,上面已经定义了什么是可迭代对象,至少我觉的这句话非常的抽象,简单的来说,就是能被for循环当做对象的,就是可迭代对象。
因为能被for循环,里面一定要有__iter__方法
In [119]: import collections.abc In [120]: a = dict() In [121]: b = list() In [122]: c = set() In [123]: d = '' In [124]: f = range(10) In [125]: isinstance(a, collections.abc.Iterable) Out[125]: True In [126]: isinstance(b, collections.abc.Iterable) Out[126]: True In [127]: isinstance(c, collections.abc.Iterable) Out[127]: True In [128]: isinstance(d, collections.abc.Iterable) Out[128]: True In [129]: isinstance(f, collections.abc.Iterable) Out[129]: True In [130]: '__iter__' in dir(f) Out[130]: True In [131]: '__iter__' in dir(d) Out[131]: True In [132]: '__iter__' in dir(a) Out[132]: True
通过collections.abc.Iterable(可迭代对象的基类),可以判断出,我上面写的那些对象都是可迭代对象,而且里面都有__iter__方法。
那我现在自己定义一个可迭代对象:
In [137]: class My_Iterable:
...: def __iter__(self):
...: print('我是可迭代对象')
...: pass
...:
In [138]:
In [138]: it = My_Iterable()
In [139]: isinstance(it, collections.abc.Iterable)
Out[139]: True
In [140]: for i in it:
...: print(i)
...:
我是可迭代对象
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-140-d3d1294ef949> in <module>
----> 1 for i in it:
2 print(i)
3
TypeError: iter() returned non-iterator of type 'NoneType'
In [141]:
从上面的代码里面可以看到,我自己定义了一个可迭代对象,这是一个可迭代对象,也能被for循环调用,但因为我的__iter__返回了不是迭代器,所以报错了。
注意这局提示:TypeError: iter() returned non-iterator of type 'NoneType',但明显我__iter__里面的print输出被执行了。
这些列子可以证明了前面说的只要有__iter__就是可迭代对象,能被for循环调用。
这里有一个参考资料,为什么for循环要调用___iter__:https://www.zhihu.com/question/44015086/answer/119281039
迭代器只要拥有__iter__与__next__属性就是迭代器(Iterator)。
概念又是非常抽象的玩意,这个有点不好用通俗的书,初学者可以简单的认为既能被for循环调用,又能被next函数调用的对象就是迭代器。
Python的内置对象中,基本没有现成的迭代器,当然可以通过iter方法产生迭代器(后面讲),还有一个就是用生成器的生成式。
In [144]: class My_Iterator: ...: def __iter__(self): ...: print('我是可迭代对象') ...: pass ...: def __next__(self): ...: pass ...: ...: In [145]: my = My_Iterator() In [146]: isinstance(my, collections.abc.Iterator) Out[146]: True
上面定义了一个对简单的迭代器,就因为里面有了两个定义的属性(属性跟方法其实都差不多,可调用的属性就是方法)。
我们知道通过iter的函数可以将一个Python中的常见可迭代对象(字典,集合,字符串,元祖,列表等等)变成迭代器,其实这里函数再Python内部定义为
def iter(source, sentinel=None): # known special case of iter """ iter(iterable) -> iterator iter(callable, sentinel) -> iterator Get an iterator from an object. In the first form, the argument must supply its own iterator, or be a sequence. In the second form, the callable is called until it returns the sentinel. """ pass
但具体的操作实在看不出来,根据我的实际来看,对象的__repr__输出肯定改变了,还有多了一些迭代器的方法,少了一些操作对象方法。简单用一个字符串来演示。
In [152]: string = 'sidian' In [153]: o_arr = dir(string) In [154]: string_iter = iter(string) In [155]: n_arr = dir(string_iter) In [156]: string Out[156]: 'sidian' In [157]: string_iter Out[157]: <str_iterator at 0x10b20f390> In [158]: o_arr Out[158]: ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] In [159]: set(o_arr)-set(n_arr) Out[159]: {'__add__', '__contains__', '__getitem__', '__getnewargs__', '__len__', '__mod__', '__mul__', '__rmod__', '__rmul__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'} In [160]: set(n_arr)-set(o_arr) Out[160]: {'__length_hint__', '__next__', '__setstate__'}
从上面的操作可以看到,从一个普通字符串(可迭代对象)通过函数iter变成一个迭代器,这里面其实应该执行了很多,同时发现变成了迭代器多一个关键属性__next__
记住一点,迭代器通过iter方法返回的还是他自己,所以一般的自定义迭代器__iter__一般返回自己,但也不是必须的,前面已经说出__iter__只要返回一个迭代器就好。
下面我定义一个编辑畸形的迭代器,通过for循环与next循环产生独立的不同的输出。
In [161]: class Sep_Iterator: ...: def __iter__(self): ...: return (i for i in range(5)) ...: def __next__(self): ...: return '666' ...: In [162]: sep = Sep_Iterator() In [163]: for i in sep: ...: print(i) ...: 0 1 2 3 4 In [164]: next(sep) Out[164]: '666' In [165]: next(sep) Out[165]: '666' In [166]: next(sep) Out[166]: '666'
通过上面的执行更加可以看出其实__iter__与__next__是独立的。
在一般的迭代里面__iter__返回(self)自身,因为自身本来就是迭代器,当一个for循环调用该对象时,首相看对象的__iter__方法,返回自身,因为自身本来就时迭代器,符合__iter__的返回标准,然后循环调用对象__next__方法,只到遇到StopIteration停止。这时for循环调用一个迭代器的输出。
前面的参考链接链接里面也很具体说了for循环调用迭代器的过程,我的理解简单说下。for循环首先调用对象的__iter__方法,比如一个字符串,它马上生成一个新的字符串的迭代器,然后返回出来,通过迭代器里面的__next__循环取出对象里面的值。这就时为什么,一个Python内置的普通可迭代对象能够重复的被for循环使用,因为for循环在读取他的时候,他的__iter__默认给他搞了一个新的对象出来。
可迭代对象与迭代器的区别:
1,迭代器占用的内存特别小。
2,迭代器可以保存内部的状态。
In [168]: x1 = [i for i in range(1000000)] In [169]: x2 = (i for i in range(1000000)) In [170]: x1.__sizeof__() Out[170]: 8697440 In [171]: x2.__sizeof__() Out[171]: 96 In [172]: next(x2) Out[172]: 0 In [173]: next(x2) Out[173]: 1 In [174]: next(x2) Out[174]: 2
生成器(generator),可以用简单生成器(i,for i in range(10)),写法跟列表生成器样式通用,把[]换成(),
还有可以通过自定义方法用yield生成。
生成器肯定是迭代器,更加是可迭代对象,生成器的功能是最多的
生成器是一种特殊的迭代器,特殊在哪里,就是这个特殊迭代器在制作的时候不需要自己定义__iter__与__next__
想判断某个对象迭代器还是容器,可以拿该对象为参数,分别调用iter,如果返回的对象相同就时迭代器,容器对象每次返回的都是不同的对象
而且相对迭代器有三个生成器对象的专属方法:
send
throw
close
简单的来说,迭代器只能从对象里面取值,生成器可以互动了,你还可以向对象里面送值。
yield,send,throw,close。我这里不写了,篇幅很长。
可以参考:https://blog.csdn.net/jpch89/article/details/87036970
一般用的最多也就yield及send,携程的时候要用。