可迭代(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,如果返回的对象相同就时迭代器,容器对象每次返回的都是不同的对象
而且相对迭代器有三个生成器对象的专属方法:
sendthrowclose
简单的来说,迭代器只能从对象里面取值,生成器可以互动了,你还可以向对象里面送值。
yield,send,throw,close。我这里不写了,篇幅很长。
可以参考:https://blog.csdn.net/jpch89/article/details/87036970
一般用的最多也就yield及send,携程的时候要用。
浙公网安备 33010602011771号