Python三大利器 — 迭代器


for循环是如何工作的

当我们拥有一个列表 l = [1,2,3,4,5],想取列表中的内容,有几种方式?

1
2
3
4
# 1 通过索引下标和切片取值
l = [1,2,3,4,5]
print(l[0]) # 1
print(l[0:2]) # [1, 2]
1
2
3
# 2 通过for循环取值
for i in l:
print(i)

他们的区别是,使用索引取值可以取到任意位置的值,前提是我知道这个值在什么位置,而for循环是取到每一个值,不需要关心这个值在什么位置,也不能跳过任何一个值去取其他位置的值,我们可以称作循环遍历。那么for循环到底是怎么工作的呢?

都有哪些数据类型可以被for循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for s in 'abcde':
print(s) # 返回字符串中每一个字符 a b c d e

dic = {'name':'leo','age':26}
for key in dic:
print(key) # 默认返回字典中的键 name , age

for value in dic.values():
print(value) # 返回字典中的值 leo,26

for k,v in dic.items():
print(k,v) # 返回字典中的键值对 name leo age 26

for i in 12345:
print(i) # TypeError: 'int' object is not iterable

当我们循环数字类型的时候报错了,说int类型不是 iterable(可迭代的)

迭代和可迭代协议

通过对数字类型的报错,不可被for循环的数据类型会报错 不是一个可迭代的,那么是不是说可迭代的数据类型就可以被for循环,如何判断数据类型是否可以被迭代?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from collections import Iterable
l = [1,2,3,4]
t = (1,2,3,4)
d = {1:2,3:4}
s = {1,2,3,4}
num = 123
money = 10.10
print(isinstance(l,Iterable)) # True
print(isinstance(t,Iterable)) # True
print(isinstance(d,Iterable)) # True
print(isinstance(s,Iterable)) # True
print(isinstance(num,Iterable)) # False
print(isinstance(money,Iterable)) # False

# 下面这三种也是可以被循环遍历
# f = open()
# range()
# enumerate 枚举

可以将某个数据集内的数据“一个挨着一个的取出来”,就叫做迭代
总结出一条规律来:能被for循环的就是“可迭代的”。
但是如果正着想,for怎么知道谁是可迭代的呢?为什么能被for循环?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 为什么能够被循环
# dir 可以返回这个数据类型的拥有的所有方法
# 查看列表、字典、字符串、和range的双下方法 有什么共同方法
# 求交集
ret = set(dir([]))&set(dir({}))&set(dir(''))&set(dir(range(10)))
print(ret) # 我们找一个和iterable比较相似的方法, '__iter__',
# 我们再来看看无法被迭代的数据类型 有没有__iter__方法
print('__iter__' in dir(int)) # False
print('__iter__' in dir(bool)) # False
print('__iter__' in dir(list)) # True
print('__iter__' in dir(dict)) # True
print('__iter__' in dir(set)) # True
print('__iter__' in dir(tuple)) # True
print('__iter__' in dir(range(10))) # True
print('__iter__' in dir(enumerate([]))) # True

再总结出一条新的规律: 能被for循环的就是“可迭代的”,只要是能被for循环的数据类型,就一定拥有__iter__双下方法

双下方法__iter__做了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
print([].__iter__())    # <list_iterator object at 0x0000000002308940>  迭代器 iterator
# [].__iter__() 得到了一个list_iterator
# 那么迭代器有什么作用呢?让我们来看看列表list和转换成列表_迭代器所有方法的差集
print(set(dir([].__iter__()))- set(dir([])) ) # {'__setstate__', '__length_hint__', '__next__'}

# 迭代器多出来的这三个方法的作用:
#__length_hint__ 获取迭代器中元素的长度
# print([1,2,3,4,5].__iter__().__length_hint__()) # 5 元素个数
# __setstate__ 可以指定从其他位置取值

# __next__ 一个一个的取值
# 迭代器取值
l = [1,2,3] # 列表
iterator = l.__iter__() # iterator现在是一个迭代器,他内部有.__next__()方法
print(iterator.__next__()) # 1
print(iterator.__next__()) # 2
print(iterator.__next__()) # 3
print(iterator.__next__()) # 报错 StopIteration
  1. 通过上面的例子我们发现,当一个可迭代的对象调用了iter()方法会生成一个 iterator (迭代器)
  2. 迭代器中含有_next__()方法,他可以一个一个的取值,如果我们一直取next取到迭代器里已经没有元素了,就会抛出一个异常StopIteration,告诉我们,列表中已经没有有效的元素了

可迭代协议 与 迭代器协议

根据上面的例子我们总结出以下概念:

  1. 能被for循环的数据类型都是 可迭代的 (iterable)
  2. 当这个数据类型调用.__iter__()方法会生成一个 迭代器(iterator)
  3. 迭代器.next()可以一个一个的取值
  4. for循环其实就是在使用迭代器,只有是可迭代对象或者迭代器,才能用for循环
  5. for循环的本质就是迭代器
1
2
3
4
5
for i in l:
pass
# 首先会去找l.__iter__() ==> iterator = l.__iter__()
# i = iterator.__next__()
# 当没有值的时候 自动停止结束 也不会报错
1
2
3
4
5
# 模拟for循环
l = [1,2,3,4,5]
iterator = l.__iter__() # 变成一个迭代器
while True:
print(iterator.__next__())

可迭代协议: 只要含有__iter__()方法的都是可迭代的


迭代器协议: 内部含有__next__()方法和__iter__()方法的就是迭代器

可迭代的不一定就是迭代器

  1. 迭代器:内部有__iter____next__方法 ,所以他一定是可迭代的
  2. 可迭代的不一定是迭代器,要看有没有__next__方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from collections import Iterable
    from collections import Iterator

    print(isinstance([],Iterable)) # 可迭代的 # True
    print(isinstance([],Iterator)) # 迭代器 # False ,list是可迭代的,但不是一个迭代器

    print('__iter__' in dir(range(12))) # True
    print('__next__' in dir(range(12))) # False
    print(isinstance(range(100000000),Iterable)) # True
    print(isinstance(range(100000000),Iterator)) # False , range是可迭代器,但不是一个迭代器,因为它没有__next__()方法

迭代器的好处

  1. 迭代器会从容器类型中 一个一个的取值,会把所有的值都取到。
  2. 它可以节省内存空间,迭代器并不会在内存中再占用一大块内存,而是随着循环每次生成一个,或者每次next()每次给我一个
    1
    2
    # print(range(10000000))          # 很快,但是并不会在内存中真正的生成数据
    # print(list(range(10000000))) # 强制转列表会导致崩溃,list是真正存在并存储在内存里 ,range是要一个给一个