迭代器
一般听到迭代器,想到的就是可以循环获取,比一般一次性组装多少数据在返回的方式要节省不少内存。迭代器使用的是return返回值方式来返回每一个结果(在__next__内部实现) 。
__iter__ 和 __next__
一般类中有这种方法定义的,都表示这个类的对象是迭代器对象,需要注意的是__next__方法一定要具备StopIteration信号,不然迭代器会无休止的一直执行下去。
只要一个对象定义了__iter__()方法,那么它就是可迭代对象(可迭代对象不一定是迭代器),不过一般没人只单独实现了__iter__,因为没有实现__next__方法,他就是具备缺陷的迭代,除非__iter__函数里返回的是别的迭代器而不是自身self,那就可以不实现也没关系,毕竟真的迭代的时候是迭代那个返回的迭代器。
如果一个对象同时实现了__iter__()和__next__()方法,那么它就是迭代器。
有一点值得一提就是:字符串、列表、字典、元组都不是迭代器,而只是迭代对象。(之所以能在for里循环,是因为for内部把它们先变成了迭代器)
判断一个对象是迭代器或者是迭代对象
1
from collections.abc import Iterable,Iterator
2
class TestClass():
3
def __iter__(self):
4
pass
5
# def __next__(self):
6
# pass
7
print('TestClass()是可迭代对象吗:',isinstance(TestClass(), Iterable))
8
print('TestClass()是迭代器吗:',isinstance(TestClass(), Iterator))
9
10
'''
11
结果:
12
TestClass()是可迭代对象吗: True
13
TestClass()是迭代器吗: False
14
'''
迭代探索
1
class MyIter:
2
def __iter__(self):
3
'''
4
比如我这里返回一个数组的迭代器,注意数组本身不是迭代器,
5
需要利用iter触发一下数组,执行数组对应的__iter__才可返回数值迭代器对象
6
:return: 返回一个迭代器对象
7
'''
8
return iter([1, 2, 8, 4, 56, 7])
9
if __name__ == '__main__':
10
ite = MyIter()
11
print('ite是迭代对象麽:',isinstance(ite,Iterable))
12
print('ite是迭代器麽:', isinstance(ite, Iterator))
13
# print(next(ite)) # 当对象不是迭代器的时候,是不可以使用next方法进行的迭代获取值的
14
ite = iter(ite) # 利用iter()函数触发一下就变成迭代器
15
print('ite是迭代器麽:', isinstance(ite, Iterator))
16
print(next(ite))
17
'''
18
之所以for循环可以直接迭代一个对象,是因为通过for循环对一个可迭代对象进行迭代时,
19
for循环内部机制会自动通过调用iter()方法执行可迭代对象内部定义的__iter__()方法来获取一个迭代器
20
'''
21
for j in MyIter():
22
print(j)
23
24
# 比如数组,直接next肯定报错,提示list不是迭代器,因为它只是迭代对象
25
l = [9,8,7]
26
l = iter(l) # 只有通过iter后,才将迭代对象变成迭代器返回
27
print(next(l)) # 此时我们才可以调用next一个个获取
28
'''
29
测试结果:
30
ite是迭代对象麽: True
31
ite是迭代器麽: False
32
ite是迭代器麽: True
33
1
34
1
35
2
36
8
37
4
38
56
39
7
40
9
41
'''
自定义迭代器
1
'''
2
例如:利用迭代器特性我们实现一个range()函数原理
3
4
我们知道在python3以前range()函数它就是一个数组(数值很大的时候可是很占用内存空间),
5
所以那个时候我们才建议使用xrange(),后来python3被废弃了,是因为range的内部被优化了。
6
'''
7
class MyRange:
8
def __init__(self, n):
9
self.max = n
10
self.num = 0
11
12
def __iter__(self):
13
return self # 这里返回的对象一定要是迭代器,一般返回自己(前提自己要实现__next__方法,不然自个不是迭代器也就没啥好迭代的)
14
15
def __next__(self):
16
if self.num >= self.max: # 如果没有StopIteration,迭代会一直持续下去
17
raise StopIteration
18
self.num += 1
19
return self.num - 1
20
21
if __name__ == '__main__':
22
23
# 单纯的调用next的话是不会触发__iter__函数调用的,只会触发__next__
24
obj = MyRange(3)
25
print(next(obj))
26
print(next(obj))
27
28
#正确用法
29
for i in MyRange(10): # for循环的时候,其实就是在不停的调用next函数(迭代器在执行前会先初始化一下自己,就是调用一次__iter__)
30
print(i)
生成器
说到生成器我们最先想到的一定是yield这个关键词(不过想到yield又想到协程的实现,哈哈哈),生成器也是一种特殊的迭代器,具备迭代器的全部特性。
yield
在python项目中,我们经常能到yield,其实我们可以这么认为。如果一个函数体内部使用yield关键字,这个函数就称为生成器函数,生成器函数调用时产生的对象就是生成器。生成器是一个特殊的迭代器,在调用该生成器函数时,Python会自动在其内部添加__iter__()方法和__next__()方法。
也就是说带yield的函数它就是一个迭代器
yield执行剖析
1
def test_1(num):
2
n = 0
3
print('start test_1 ...')
4
while n < num:
5
res = yield n
6
print('res:',res)
7
n += 1
8
9
def test_2():
10
print('start test_2 ...')
11
yield
12
print('我是谁?')
13
name = yield
14
print('我是',name)
15
yield
16
17
18
if __name__ == '__main__':
19
t1 = test_1(3)
20
t2 = test_2()
21
print(t1,t2) # 可以发现带有yield的函数,调用后返回的就是一个生成器对象,而且它不会立即执行,看打印就知道,先打印的是'啦啦啦'
22
print('啦啦啦') # 普通打印
23
print(next(t1)) # 开始执行,并且因为yiled返回了值n,此时n为0,返回后立马暂停退出执行main函数里的下一行
24
print(next(t1)) # 继续执行上次t1暂停的地方,yield因为上次返回掉了结果,所以继续的时候值为None赋值给了res,随后循环,继续yiled返回n,此时为1,继续暂停
25
print(next(t2)) # 开始执行t2,因为没返回值,所以打印None
26
print(t1.send(666)) # 继续接着t1的上次yield,发送了666,所以在继续前,将666赋值给res,有开始下一次循环,此时n为2
27
next(t2) # 继续接着执行t2上次yiled处,并且暂停
28
t2.send('Pocket') # 继续执行t2上次yield,并且暂停,不过此时因为main里没有要执行的了,所以进程结束
29
30
31
'''
32
执行结果:
33
34
<generator object test_1 at 0x10300fed0> <generator object test_2 at 0x10300fe58>
35
啦啦啦
36
start test_1 ...
37
0
38
res: None
39
1
40
start test_2 ...
41
None
42
res: 666
43
2
44
我是谁?
45
我是 Pocket
46
47
Process finished with exit code 0
48
'''
简单编写一个生成器
1
'''
2
模拟一个python2.7时的xrange实现,可以看出比上面的Range类实现优雅很多
3
'''
4
def xrange(num: int) -> object:
5
n = 0
6
while n < num:
7
yield n # 此处有点类似return作用,返回n的值后暂停(注意只是暂停,不是结束),等待下一次next的触发
8
n = n + 1
9
10
if __name__ == '__main__':
11
ob = xrange(5)
12
print(ob) # 可以发现ob这个对象就是一个生成器对象
13
l = [i for i in ob]
14
print(l)
15
16
'''
17
测试结果:
18
<generator object xrange at 0x10cde7ed0>
19
[0, 1, 2, 3, 4]
20
'''
列表解析式和生成器解析式
两者在写法上其实很像,不过列表解析式是使用[]符号,返回数组。
生成器解析式是使用()符号,返回生成器。共同特点都是只有迭代器才具备这种写法(ps:生成器也是一种特殊的迭代器)
1 | # 列表解析式 |
2 | l = [i*2 for i in range(10) if i % 2 == 0] |
3 | print(l) |
4 | |
5 | # 生成器解析式 |
6 | g = (i*2 for i in range(10) if i % 2 == 0) |
7 | print(next(g)) |