跳转至

一、迭代器

迭代器引入

 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,会抛出异常

1
2
for item in stu_sys:
    print(item) 

抛出异常

1
2
     for item in stu_sys:
TypeError: 'StudentSystem' object is not iterable

什么是迭代

迭代是访问集合元素的一种方式

可迭代对象

可以直接作用于for循环的对象统称为可迭代对象(Iterable)。内部含有__iter__方法。如字符串、列表、元组、字典、集合、文件句柄等。 可迭代对象.__iter__():可得到迭代器对象。

判断一个对象是否是可迭代对象

1
2
3
4
5
6
>>> '__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循环的工作原理

1
2
3
4
5
6
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

迭代器的优缺点

优点

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

缺点

  1. 除非取尽,否则无法获取迭代器的长度
  2. 只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行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个状态

  1. 当调用生成器函数得到生成器对象时,此时的生成器对象可以理解为处于初始状态

  2. 通过next()调用生成器对象,对应的生成器函数代码开始运行,此时生成器对象处于运行中状态

  3. 如果遇到了yield语句,next()返回时,yield语句右边的对象作为 next()的返回值;生成器在yield语句所在的位置暂停当再次使用next()时继续从该位置继续运行

  4. 如果执行到函数结束,则抛出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)