一、迭代器
迭代器引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | class StudentSystem:
def __init__(self):
self.stus = []
def add(self, name, age, tel):
new_stu = dict()
new_stu["name"] = name
new_stu["age"] = age
new_stu["tel"] = tel
self.stus.append(new_stu)
stu_sys = StudentSystem()
stu_sys.add("张三", 18, "18888888888")
stu_sys.add("李四", 19, "18811112222")
stu_sys.add("王五", 20, "18833334444")
|
上面的代码中,如何使用for循环遍历所有的学生信息?如果简单的遍历stu_sys,会抛出异常
| for item in stu_sys:
print(item)
|
抛出异常
| for item in stu_sys:
TypeError: 'StudentSystem' object is not iterable
|
什么是迭代
迭代是访问集合元素的一种方式
可迭代对象
可以直接作用于for
循环的对象统称为可迭代对象(Iterable)。内部含有__iter__
方法。如字符串、列表、元组、字典、集合、文件句柄等。
可迭代对象.__iter__()
:可得到迭代器对象。
判断一个对象是否是可迭代对象
| >>> '__iter__' in dir('hello')
True
# 或者
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
|
迭代器
迭代器是一个可以记住遍历位置的对象,迭代器对象从第一个元素开始访问,直到所有的元素被访问结束,迭代器只能往前不会后退。
迭代器的本质
迭代器指的是迭代取值的工具,可以通过iter()函数获取可迭代对象的迭代器,然后对获取到的迭代器不断调用next()函数来获取下一条数据。
获取可迭代对象的迭代器
通过iter()
能够得到一个可迭代对象的迭代器,可以通过next()
函数获取下一条数据
方式一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | >>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d_iterator = d.__iter__()
>>> print(d_iterator)
<dict_keyiterator object at 0x0000019318519598>
>>>
>>> print(d_iterator.__next__())
a
>>> print(d_iterator.__next__())
b
>>> print(d_iterator.__next__())
c
>>> print(d_iterator.__next__())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
|
方式二
1
2
3
4
5
6
7
8
9
10
11
12 | >>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d_iter = iter(d)
>>> next(d_iter)
'a'
>>> next(d_iter)
'b'
>>> next(d_iter)
'c'
>>> next(d_iter)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
|
for循环的工作原理
| d = {'a': 1, 'b': 2, 'c': 3}
# 1、d.__iter__()得到一个迭代器对象
# 2、迭代器对象.__next__()拿到一个返回值,然后将该返回值赋值给k
# 3、循环往复步骤2,直到抛出StopIteration异常for循环会捕捉异常然后结束循环
for k in d:
print(k)
|
自定义迭代器
__iter__
方法
上面提到的iter()方法必须是对可迭代对象才能取到迭代器对象,对于自定义类,只要在类中定义__iter__
方法,那么这个类创建出来的对象一定是可迭代对象。即一个具备了__iter__
方法的对象就是一个可迭代对象.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | from collections.abc import Iterable
class StudentSystem:
def __init__(self):
self.stus = []
def add(self, name, age, tel):
new_stu = dict()
new_stu["name"] = name
new_stu["age"] = age
new_stu["tel"] = tel
self.stus.append(new_stu)
def __iter__(self):
pass
stu_sys = StudentSystem()
stu_sys.add("张三", 18, "18888888888")
stu_sys.add("李四", 19, "18811112222")
stu_sys.add("王五", 20, "18833334444")
print(isinstance(stu_sys, Iterable)) # True
|
__next__
方法
迭代器是用来记录每次迭代访问到的位置,当对迭代器使用next()
函数的时候,迭代器会返回它所记录位置的下一个位置的元素。
实际上在使用next()
函数的时候,调用的就是迭代器对象的__next__
方法,所以想要构造一个迭代器,就是要实现它的__next__
方法。
但是这还不够,python要求迭代器本身也是可迭代的,所以还要为迭代器实现__iter__
方法,而__iter__
方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__
方法返回自身即可。
一个实现了__iter__
方法和__next__
方法的对象,就是迭代器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 | from collections.abc import Iterable
from collections.abc import Iterator
class StudentSystem:
"""自定义一个可迭代对象"""
def __init__(self):
self.stus = []
def add(self, name, age, tel):
new_stu = dict()
new_stu["name"] = name
new_stu["age"] = age
new_stu["tel"] = tel
self.stus.append(new_stu)
def __iter__(self):
# 1. 标记当前类创建出来的对象是可迭代对象
# 2. 当调用iter()函数时,这个方法会被自动调用它返回自己指定的那个迭代器
return MyIterator()
class MyIterator:
"""自定义供上面可迭代对象使用的一个迭代器"""
def __init__(self):
pass
def __next__(self):
# 1. 标记当前类创建出来的对象一定是迭代器
# 2. 当调用next()函数的时候,这个方法会被自动调用,它返回一个数据
pass
def __iter__(self):
pass
stu_sys = StudentSystem() # 可迭代对象
stu_sys_iter = iter(stu_sys) # 用iter(stu_sys)就是获取stu_sys这个可迭代对象的迭代器,
# 它会自动调用stu_sys这个对象的__iter__方法,
# 这个方法返回的值就当做iter()函数的返回值
print(isinstance(stu_sys, Iterable)) # True
print(isinstance(stu_sys, Iterator)) # False
print(isinstance(stu_sys_iter, Iterable)) # True
print(isinstance(stu_sys_iter, Iterator)) # True
|
迭代器对象一定是可迭代对象,可迭代对象不一定是迭代器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 | class StudentSystem:
"""自定义一个可迭代对象"""
def __init__(self):
self.stus = []
def add(self, name, age, tel):
new_stu = dict()
new_stu["name"] = name
new_stu["age"] = age
new_stu["tel"] = tel
self.stus.append(new_stu)
def __iter__(self):
# 1. 标记当前类创建出来的对象是可迭代对象
# 2. 当调用iter()函数时,这个方法会被自动调用它返回自己指定的那个迭代器
return MyIterator(self)
class MyIterator:
"""自定义供上面可迭代对象使用的一个迭代器"""
def __init__(self, stu_obj):
self.stu_obj = stu_obj
self.current = 0
pass
def __next__(self):
# 1. 标记当前类创建出来的对象一定是迭代器
# 2. 当调用next()函数的时候,这个方法会被自动调用,它返回一个数据
if self.current < len(self.stu_obj.stus):
item = self.stu_obj.stus[self.current]
self.current += 1
return item
else:
raise StopIteration
def __iter__(self):
pass
stu_sys = StudentSystem()
stu_sys.add("张三", 18, "18888888888")
stu_sys.add("李四", 19, "18811112222")
stu_sys.add("王五", 20, "18833334444")
for stu in stu_sys:
print(stu)
|
优化上面的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | class StudentSystem:
"""自定义一个可迭代对象"""
def __init__(self):
self.stus = []
self.current = 0
def add(self, name, age, tel):
new_stu = dict()
new_stu["name"] = name
new_stu["age"] = age
new_stu["tel"] = tel
self.stus.append(new_stu)
def __iter__(self):
return self
def __next__(self):
if self.current < len(self.stus):
item = self.stus[self.current]
self.current += 1
return item
else:
self.current = 0 # 解决第二次for循环没有值的问题
raise StopIteration
stu_sys = StudentSystem()
stu_sys.add("张三", 18, "18888888888")
stu_sys.add("李四", 19, "18811112222")
stu_sys.add("王五", 20, "18833334444")
for stu in stu_sys:
print(stu)
|
通过for循环之外的方法使用迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | class StudentSystem:
"""自定义一个可迭代对象"""
def __init__(self):
self.stus = []
self.current = 0
def add(self, name, age, tel):
new_stu = dict()
new_stu["name"] = name
new_stu["age"] = age
new_stu["tel"] = tel
self.stus.append(new_stu)
def __iter__(self):
return self
def __next__(self):
if self.current < len(self.stus):
item = self.stus[self.current]
self.current += 1
return item
else:
self.current = 0
raise StopIteration
stu_sys = StudentSystem()
stu_sys.add("张三", 18, "18888888888")
stu_sys.add("李四", 19, "18811112222")
stu_sys.add("王五", 20, "18833334444")
for stu in stu_sys:
print(stu)
print(list(stu_sys)) # [{'name': '张三', 'age': 18, 'tel': '18888888888'}, {'name': '李四', 'age': 19, 'tel': '18811112222'}, {'name': '王五', 'age': 20, 'tel': '18833334444'}]
print(tuple(stu_sys)) # ({'name': '张三', 'age': 18, 'tel': '18888888888'}, {'name': '李四', 'age': 19, 'tel': '18811112222'}, {'name': '王五', 'age': 20, 'tel': '18833334444'})
|
迭代器案例
使用迭代器实现一个二元一次方程:y=2x+1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | class PointXY:
"""通过迭代器,生成不确定个数点的坐标"""
def __init__(self):
self.x = 0
def __iter__(self):
return self
def __next__(self):
y = 2 * self.x + 1
point_x_y = (self.x, y)
self.x = y
return point_x_y
point = PointXY()
print(next(point)) # (0, 1)
print(next(point)) # (1, 3)
print(next(point)) # (3, 7)
print(next(point)) # (7, 15)
|
斐波那契数列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | class FibIterator:
def __init__(self):
self.num1 = 1
self.num2 = 1
def __iter__(self):
return self
def __next__(self):
tmp_num = self.num1
self.num1, self.num2 = self.num2, self.num1 + self.num2
return tmp_num
fib = FibIterator()
print(next(fib)) # 1
print(next(fib)) # 1
print(next(fib)) # 2
print(next(fib)) # 3
print(next(fib)) # 5
|
迭代器的优缺点
优点
- 为序列和非序列类型提供了一种统一的迭代取值方式。
- 惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。
缺点
- 除非取尽,否则无法获取迭代器的长度
- 只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行
next
方法直到值取尽,否则就会停留在某个位置,等待下一次调用next
;若是要再次迭代同个对象,你只能重新调用iter
方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。
迭代器存储是生成数据的算法。
二、生成器
利用迭代器也可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。
但是在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状委生成下一个数据。为了达到记录当前状态,并配合next()
函数进行迭代使用,
简言之,迭代器是可以实现在循环的过程中生成数据的,但是稍稍有些复杂.有一个能具有迭代器的功能,且比它更加简单的方式:生成器(generator)。生成器是一类特殊的迭代器。
1.创建生成器的方法
1. 把列表生成式的[]
改为()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | >>> g = (x * x for x in range(10) if x % 3 == 0)
>>> print(next(g))
0
>>> print(next(g))
9
>>> print(next(g))
36
>>> print(next(g))
81
>>> print(next(g))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> g
<generator object <genexpr> at 0x0000019318767A98>
|
2. 使用yield关键字
1. yeild执行过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | >>> def func():
... print('第一次')
... yield 1
... print('第二次')
... yield 2
... print('第三次')
... yield 3
... print('第四次')
...
>>> g=func()
>>> print(g)
<generator object func at 0x0000019318767A98>
>>> next(g)
第一次
1
>>> next(g)
第二次
2
>>> next(g)
第三次
3
>>> next(g)
第四次
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> #next() 会触发函数体代码的运行,然后遇到yield停下来,将yield后的值当做本次调用的结果返回
...
|
执行过程:
1. 如果是第一次执行,则从def代码块的开始部分执行,直到遇到yield为止,并且把yield关键字后的数值返回,当做next()
的返回值
2. 如果不是第一次执行,则从上一次暂停的位置执行(即从上一次yield关键字的下一个语句开始执行),直到遇到下一次yield为止,并且把yield键字后的数值返回,当做next()
的返回值
3. 如果函数运行结束(即遇到return)则抛出StopIteration异常
有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值。
通常把含有yield的函数称为生成器函数,把调用生成器函数返回的结果称为生成器。
yield关键字改变了函数的性质:
1. 调用生成器函数不是直接执行其中的代码,而是返回一个对象
2. 生成器函数内的代码,需要通过生成器对象来执行。
使用迭代器实现斐波那契数列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | def fib_generator():
num1 = 1
num2 = 1
while True:
tmp_num = num1
num1, num2 = num2, num1 + num2
yield tmp_num
fib = fib_generator()
print(fib) # <generator object fib_generator at 0x000001AEC6FBAA50>
print(next(fib)) # 1
print(next(fib)) # 1
print(next(fib)) # 2
print(next(fib)) # 3
print(next(fib)) # 4
|
2. 获取生成器中return的返回值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | def fib_generator():
num1 = 1
num2 = 1
tmp_num = num1
num1, num2 = num2, num1 + num2
yield tmp_num
tmp_num = num1
num1, num2 = num2, num1 + num2
yield tmp_num
tmp_num = num1
num1, num2 = num2, num1 + num2
yield tmp_num
return "结束"
fib = fib_generator()
print(next(fib)) # 1
print(next(fib)) # 1
print(next(fib)) # 2
try:
print(next(fib))
except StopIteration as ret:
print(ret.value) # 结束
|
3. send
如果想要让生成器继续向下开始运行,我们可以使用next或者send。
两者都会让生成器继续向下运行,如果运行时,遇不见yield,那么都会产生异常。
但是next只会让运行继续开始,而send除了可以让其开始运行之外,还可以将某个数据携带过去
1
2
3
4
5
6
7
8
9
10
11
12 | def g_fun():
while True:
print("----1----")
num = yield 100
print("----2----", "num=", num)
g = g_fun()
print(next(g))
print(next(g))
print(next(g))
print(g.send(200))
print(g.send(300))
|
执行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | ----1----
100
----2---- num= None
----1----
100
----2---- num= None
----1----
100
----2---- num= 200
----1----
100
----2---- num= 300
----1----
100
|
2. 生成器的4个状态
-
当调用生成器函数得到生成器对象时,此时的生成器对象可以理解为处于初始状态
-
通过next()
调用生成器对象,对应的生成器函数代码开始运行,此时生成器对象处于运行中状态
-
如果遇到了yield语句,next()
返回时,yield语句右边的对象作为 next()
的返回值;生成器在yield语句所在的位置暂停当再次使用next()
时继续从该位置继续运行
-
如果执行到函数结束,则抛出StopIteration异常。不管是使用了return语句显式地返回值,或者默认返回None值,返回值都只能作为异常的值一并抛出,此时的生成器对象处于结束的状态;对于已经结束的生成器对象再次调用next()
,直接抛出StopIteration异常,并且不含返回值
3. 生成器案例
使用生成器实现一个二元一次方程:y=2x+1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | def point_generator():
x = 0
k = 2
b = 1
while True:
y = k * x + b
tmp = yield (x, y)
if tmp:
k, b = tmp
x = y
point = point_generator()
print(next(point)) # (0, 1)
print(next(point)) # (1, 3)
print(next(point)) # (3, 7)
print(next(point)) # (7, 15)
print(point.send((3, 2))) # (15, 47)
print(point.send((3, 2))) # (47, 143)
print(point.send((3, 2))) # (143, 431)
|