迭代器和生成器在很多编程语言中都会以不同形式的存在,在实际编程中经常会被用到。下面我们来了解一下什么是迭代。

1 迭代

了解Java的AIAS行者(AI行者是我B站粉丝勋章名称)应该都会知道,在Java中,可以通过List集合的下标来遍历List的元素。在Python中,对于可迭代对象,如list、tuple等,可以通过for循环遍历,也可以使用下标遍历,这种遍历一个“容器”中元素的过程,就叫做迭代。

Python中for循环比较强大,只要对象是可迭代类型就可以使用for循环进行迭代。例如:

# -*- coding: UTF-8 -*-

# 1、for 循环迭代字符串, 字符串之间使用空格连接
for char in 'AIASBCYD':
    print(char, end=' ')

print('\n')

# 2、for 循环迭代 list 列表,列表元素之间使用空格连接
list1 = [i for i in range(5)]  # 这里使用了列表生成表达式
for num in list1:
    print(num, end=' ')

print('\n')

# 3、for 循环迭代 dict (字典)
dict1 = {'name': '菊子皮', 'age': '26', 'sex': '男'}
# 迭代 dict 中的 key(默认是迭代key)
for key in dict1:
    print(key, end=' ')

print('\n')
# 迭代 dict 中的 value
for value in dict1.values():
    print(value, end=' ')

print('\n')

# list 中一个元素中还有多个元素
for x, y in [(1, 'a'), (2, 'b'), (3, 'c')]:
    print(x, y)

结果如下:

A I A S B C Y D

0 1 2 3 4

name age sex

菊子皮 26 男

1 a
2 b
3 c

2 Python中的迭代器

在上面看到了迭代过程,迭代是Python中非常强大的功能之一。但是,啥是迭代器呢?迭代器,迭代器是一个可以记住遍历的位置的对象。它具有的特征:

  • 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
  • 迭代器只能往前不会后退。

迭代器有两个基本的方法:iter()next()。字符串,列表或元组对象都可用于创建迭代器,迭代器对象可以使用常规 for 语句进行遍历,也可以使用 next() 函数来遍历。for的底层实现就是使用迭代器完成的。下面,来看看几个实例:

# 1、字符创创建迭代器对象
str1 = 'AIASBCYD'
# 使用iter内建函数构建迭代器对象
iter1 = iter(str1)
# 使用next内建函数访问元素
print(next(iter1))  # 每运行一次返回一个对象
# 使用for循环遍历迭代器对象
for ele in iter1:
    print(ele, end=' ')

print('\n------------------------')

# 2、list对象创建迭代器
list1 = [i for i in range(5)]
iter2 = iter(list1)
# next() 函数遍历迭代器
while True:
    try:
        print(next(iter2))
    except StopIteration:
        break

3 Python中的生成器

在前面的内容中,可以看到使用列表生成表达式创建列表,其是一个非常简单和便捷的方式。当然,也可以使用直接创建的方式list1 = [0, 1, 2, 3, 4]。但是,直接创建的方式会受到内存的限制。当数据量特别大的时候,将数据一次性加载到内存中显然是不合适的。这就是为什么有些小白在做机器学习的时候,一次性将几个G的数据读到内存后,电脑就崩了(电脑运存本来还不大)。而且,创建一个包含 非常多的元素的列表,不仅占用很大的存储空间,在只需要访问前面几个元素时,那后面绝大多数元素占用的空间都白白浪费了。

在Python中,使用生成器(genertor)可以构建出一种一边循环一边计算的机制。那么,这样就不必创建完整的 list,从而节省大量的空间。使用yield的函数就被称为生成器(generator)。

生成器跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,也可以理解为生成器就是一个迭代器。运行过程:在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值,在下一次执行 next()方法时从当前位置继续运行。下面来看看几个案例。

3.1 创建生成器

最简单的方式就是使用类似于列表、元组、字典等的推导表达式构建。如:

# 需要使用()包裹
gen = (2*i for i in range(5))
# 查看生成器
print(gen)
"""
<generator object <genexpr> at 0x000001544475AF48>
"""

3.2 遍历生成器中的元素

这里就可以使用for循环的方式对迭代器进行遍历。当然也可以使用next()方法进行遍历。

for num in gen:
    print(num, end=' ')

print('\n------------------------')
gen = (2*i for i in range(5))
while True:
    try:
        print(next(gen), end=' ')
    except StopIteration:
        break
"""
0 2 4 6 8
------------------------
0 2 4 6 8
"""

注意:这种类型生成器返回所有元素之后,就不可再生成元素了,也就是说:只能对其迭代一次。

3.4 构建函数形式的生成器

在实际编程和开发过程中,使用多的还是以函数的形式实现生成器。从下面这个函数改进:

def fun():
    for i in range(5):
        print(i)


fun()
"""
0
1
2
3
4
"""

将其改进为一个生成器。

def fun():
    for i in range(5):
        yield i


print(fun())
"""
<generator object fun at 0x0000021A3C62E948>
"""

上面的函数简单,但是没有体现出生成器的优势。生成器最好的应用应该是:如果你不想同一时间将所有计算出来的大量结果集分配到内存当中,特别是结果集里还包含循环,因为这样会耗很大的资源。例如:计算斐波那契数列的生成器:

def fib(n):
    a, b = 1, 1
    for i in range(n):
        yield a
        a, b = b, a + b


# 运行
for n in fib(10000):
    print(n, end=' ')

程序运行过程。

while迭代器python python for迭代器_迭代器


可以看出,运行一个这么大的参数,也不存在有卡死的状态。因为这种方式不会使用太多的资源。

需要注意得是: generator 和函数的执行流程不一样。函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。而变成 generator 的函数后,在每次调用 next() 的时候执行,遇到 yield语句返回,再次执行时从上次返回的 yield 语句处继续执行。这有点类似于中断。从下面得例子也可以看出这种效果。

def test():
    print('step 1')
    yield('A')
    print('step 2')
    yield('B')
    print('step 3')
    yield('C')


t = test()
print(next(t))
print(next(t))
print(next(t))
"""
step 1
A
step 2
B
step 3
C
"""

从上面的例子可以看出test()已经不是一个普通函数了,而变成了一个generator,在执行过程中,遇到yield后就中断,再次调用时再继续执行。当generator所有内容执行完毕后,再执行就会报错。这时就需要进行错误捕获了。