Python三大器之迭代器

迭代器协议

  迭代器协议规定:对象内部必须提供一个__next__方法,对其执行该方法要么返回迭代器中的下一项(可以暂时理解为下一个元素),要么就引起一个Stopiteration异常以终止迭代。(当所有元素被取干净后其实内部就会自动触发Stopiteration

  可迭代对象是指对象内部必须提供一个__iter__方法,并且只要是可迭代对象那么就可以通过__iter__方法创造出该对象所专属的迭代器。

  注意:

   1.迭代器(Iterator):必须有__next__方法才能被叫做迭代器。

   2.可迭代对象(Iterable):必须有__iter__方法才能被叫做可迭代对象

  两者之间的关系:

   1.迭代器是可迭代对象,

   2.可迭代对象不一定是迭代器,

初识迭代器

什么是迭代器

  迭代器指的是迭代取值的一种工具。

  迭代指的是一个重复的过程,每次重复都必须基于上一次的结果而继续,单纯的重复并不是迭代。

# ==== 迭代与非迭代的区别 ====

count = 0
while count < 5:
    print(count)
    count += 1
# 以上的每一次重复动作都是基于上一次重复动作,可以称之为迭代。
while 1:
    input(">>>")
# 以上不可称之为迭代,下一次的重复动作与上一次没有任何关系。

为什么要有迭代器

  对于列表等具有索引的数据类型来说,取出他们的值可以使用索引。 而对于字典集合等等不提供索引的数据类型来说,Python必须为其提供一种能够不依赖于索引的取值方式。这种方式就被称为迭代器。

怎么使用迭代器

  迭代器可以通过循环进行取值。下面的章节将详细介绍迭代器及其内部原理,学完本章节你就能随心所欲的驾驭迭代器了。

迭代器详解

迭代器和可迭代对象的区别与关系

  1.迭代器(Iterator):必须有__next__方法才能被叫做迭代器。

  2.可迭代对象(Iterable):必须有__iter__方法才能被叫做可迭代对象

  基于这两句话,我们可以为学过的数据类型做一个区分:

[].__iter__() # list具有 __iter__ 方法,是可迭代对象
[].__next__() # list不具有 __next__方法,不是迭代器
# AttributeError: 'list' object has no attribute '__next__'

  他们的关系在于:

  通过可迭代对象的__iter__方法。可以为其创造一个专属的迭代器。

  示例如下:

# ==== 迭代器和迭代对象的区分 ====
from collections.abc import Iterable  # 可以用于判断是否是可迭代对象
from collections.abc import Iterator  # 可以用于判断是否是迭代器

li = [1, 2, 3, 4, 5]
# isinstance 可以用来判断一个对象是否属于另一个对象
print(isinstance(li, Iterable))  # True 判断是否属于可迭代对象,说明具有__iter__方法
print(isinstance(li, Iterator))  # False 判断是否属于迭代器,说明没有__next__方法。

li_iterator = iter(li)  # iter方法就是调用对象内置的__iter__方法。
print(li_iterator)  # <list_iterator object at 0x0000021AC6BB3AF0>
print(isinstance(li_iterator, Iterable))  # True
print(isinstance(li_iterator, Iterator))  # True 
# 可以看到。iter方法就是为可迭代对象li创建出了一个专属迭代器。

数据类型迭代类型一览

  我们可以通过导入collections.abs下的包或者手动输入每种数据类型查看是否具有__next__方法的方式做一个总结:

# ==== 实验过程 ====
from collections.abc import Iterable  # 可以用于判断是否是可迭代对象
from collections.abc import Iterator  # 可以用于判断是否是迭代器

st = "123"
print(isinstance(st,Iterable)) # True
print(isinstance(st,Iterator)) # False

li = [1,2,3]
print(isinstance(li,Iterable)) # True
print(isinstance(li,Iterator)) # False

tu = (1,2,3)
print(isinstance(tu,Iterable)) # True
print(isinstance(tu,Iterator)) # False

di = {"k1":"v1"}
print(isinstance(di,Iterable)) # True
print(isinstance(di,Iterator)) # False

se = {1,2,3}
print(isinstance(se,Iterable)) # True
print(isinstance(se,Iterator)) # False

with open(file="test.text",mode="w",encoding="utf-8") as f:
    print(isinstance(f, Iterable))  # True
    print(isinstance(f, Iterator))  # True
# === 了解 === Ps:以下创建方式均为创建出经过优化的可迭代对象。并非迭代器,更并非生成器。

ra = range(1,11)
print(isinstance(ra,Iterable)) # True
print(isinstance(ra,Iterator)) # False

di_keys = di.keys()
print(isinstance(di_keys,Iterable)) # True
print(isinstance(di_keys,Iterator)) # False

di_values = di.values()
print(isinstance(di_values,Iterable)) # True
print(isinstance(di_values,Iterator)) # False

di_items = di.items()
print(isinstance(di_items,Iterable)) # True
print(isinstance(di_items,Iterator)) # False

se_fro = frozenset(se)
print(isinstance(se_fro,Iterable)) # True
print(isinstance(se_fro,Iterator)) # False

  Ps:关于什么叫做经过优化的可迭代对象。会在结束迭代器和生成器的学习后专门开辟一章节来讲。

  五大基本容器数据类型(listtupledictsetstr)等都是属于可迭代对象。本身并不属于迭代器。

  文件句柄对象本身是属于迭代器。

  此外,range()方法产生的数据类型是属于经过优化的可迭代对象。 以及,dictkeysitemsvalues等方法产生的数据类型也是属于经过优化的可迭代对象。

循环与迭代器

while循环取值与迭代器的作用体现

  迭代器的作用在于:对于没有索引的数据类型,对其遍历取值必须通过迭代器下的__next__方法来完成。 并且迭代器中的所有值一旦被取出来,该迭代器将不能被二次应用。

# ==== 迭代器的多次迭代示例 ====
# 方式1 : 针对具有索引的数据类型取值,可以不依赖于迭代器
li = [1,2,3,4,5]
index = 0
while index < len(li):
    print(li[index])
    index += 1
print("针对具有索引的数据类型取值完毕 ---")


# 方式2 :针对不具有索引的数据类型取值,必须依赖迭代器下的__next__方法。
dic = dict(k1="v1",k2="v2",k3="v3")
dic_ator = dic.__iter__() # 创建出专属迭代器,迭代器具有__next__方法
while 1:
    try:
        print(dic_ator.__next__())
        # 或者用 next(li_ator)
    except Exception as e:
        breakprint(" ----> 第二次取值")

while 1:
    try:
        print(dic_ator.__next__())
        # 或者用 next(li_ator)
    except Exception as e:
        break# ==== 执行结果 ==== Ps:可以看到。一个迭代器将所有值全部取出后,再对其进行遍历已经取不出值了。
"""
1
2
3
4
5
针对具有索引的数据类型取值完毕 ---
k1
k2
k3
 ----> 第二次取值
"""
迭代器的多次迭代示例

for循环底层原理

  for循环本质就是while循环 为什么说它方便。

    1.使用for循环会自动的为被迭代的可迭代对象创建出一个专属的迭代器。

    2.for循环会不断的调用迭代器中的__next__方法。

    3.当迭代器中所有的值全部被__next__取出后在对其进行取值会出发StopIterator的异常,而for循环可以自动的来捕捉这种异常。

  为什么Python要将除开文件对象外的所有数据类型作为可迭代对象而不直接做成迭代器:迭代器无法重复使用

# ==== for循环的底层原理 ====
# for循环本质就是while循环
dic = dict(k1="v1",k2="v2",k3="v3")
dic_ator = dic.__iter__() # 第一件事:创建专属迭代器
while 1:
    try: # 第三件事: 捕捉异常
        print(dic_ator.__next__()) # 第二件事,不断执行该迭代器下的__next__方法
        # 或者用 next(li_ator)
    except Exception as e:
        break# 每次for循环都会完成上述三步骤,
# 这也是为什么Python要将除开文件对象外的其他数据类型作为可迭代对象的原因(便于重复利用)
for i in dic:
    print(i)
print("第一次结束")
for i in dic:
    print(i)
print("第二次结束")
# 如果循环的是一个纯粹的迭代器,那么第二次的for循环根本就不能读出任何内容。迭代器值空=迭代器死亡
for循环的底层原理

迭代器小结

优点

  1.为序列和非序列类型提供了一种统一的迭代取值方式。(序列就是有序的,支持索引的数据类型,非序列是无序的,不支持索引的数据类型,如dictset等等)

  2.惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用__next__来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。

  3.Python中为了能让一个容器类型能被多次取值。干脆将他们全部都做成可迭代对象,当对其调用for循环时会自动创建迭代器,每一次for循环都创建出一个新的迭代器。

缺点

  1、除非取尽,否则无法获取迭代器的长度

  2、只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行__next__方法直到值取尽,否则就会停留在某个位置,等待下一次调用__next__;若是要再次迭代同个对象,你只能重新调用__iter__方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

  3、Python中的容器类型本身就占据一定的内存空间,如:[1,2,3]。对其转换为迭代器只是为了方便多次被for循环调用,所以对可迭代对象每次for循环时创建出迭代器这种做法并不会节省很多内存。

疑点 - 如何自定义迭代器

  我们目前创造迭代器的方式都是使用可迭代对象的__iter__方法,那么这种创建迭代器的方式无疑是脱裤子放屁。

    1. 诸如listtupledictsetdict等可迭代对象本身没经历任何优化,他们的存值占据了内存空间。

    2. 迭代器本身具有惰性求值的特点,目的就是为了节省内存,但是对上述可迭代对象创建专属迭代器只是为了能够让其被多次调用。

  我们怎么样才能自己创建一个迭代器,而不是通过上述的可迭代对象调用其__iter__方法再创建一个迭代器呢?答案在下一章的生成器中。

posted @ 2020-05-18 11:36  云崖先生  阅读(347)  评论(1编辑  收藏  举报