5.12 迭代器(Iterator)

在python中,一边循环一边计算后面元素的机制(惰性运算),称为迭代器:Iterator。

迭代是Python最强大的功能之一,是访问集合元素的一种方式。

迭代器有两个基本的方法:iter() 和 next()。
或者 .iter() 和 next() 方法。

for的表达式用()括起来可以创建一个迭代器

  • 迭代器的特点:
  • 迭代器一定是一个可循环的(for,while等)
  • 迭代器是一个可以记住遍历的位置的对象(迭代器内部有一个计数器,会记录执行到第几次循环)
  • 迭代器只能往前不会后退。
  • 迭代器进行下一步用next()函数。(也可以用 .next() 方法)
  • 当执行到循环最后一次,继续next()则会报错StopIteration
  • next(g)执行到一半,然后用'for i in g'来循环的话,会从记录到的位置开始继续循环

python3中,用for循环循环可迭代数据类型的时候,其实内部先使用iter把数据转成迭代器,再用next执行迭代器。

迭代器例1:

g = (x*x for x in range(3))  # 生成一个迭代器。迭代器赋值只赋值了算法,并没有值生成。这个写法其实是一个生成器,语法糖里有不可见的yield
print(g)  # 打印一个迭代器对象的内存地址,没有值
print(next(g))  # 执行迭代器用next(),一次next只返回循环内的一次运算结果。
print(next(g))  # 返回第二次循环内运算的结果。
print(next(g))  # 返回第三次循环内运算的结果。
# print(next(g))  # next超过循环的最大次数,报错StopIteration

执行结果:

<generator object <genexpr> at 0x00000000020B3570>
0
1
4

generator(生成器)是迭代器的一种

迭代器例2:

g = (x*x for x in range(6))  
print(next(g))  # 执行迭代器用next(),一次next只返回循环内的一次运算结果。
print(next(g))  # 返回第二次循环内运算的结果。

for i in g:  # 这样可以遍历迭代器器剩下的所有元素。因为迭代器内部有计数器,所以会接着上次next()执行到的位置开始循环
    print(i)  # 这里打印4
    
# print(next(g))  # 因为for遍历了所有迭代器元素,计数器就到了最后。所以这里会报错

执行结果:

0
1
4
9
16
25

python3的range()用的就是迭代器的算法。py2则是把for循环的元素放入一个列表,很低效。

5.13 生成器 (generator) yield

生成器:python中,使用了yield的函数被称为生成器(generator)

yield:类似于return,返回b。但是与return不同,不会中断程序。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

生成器的yield必须在循环内(迭代器必须是可迭代的)

功能在下面的代码例里面注释说明:

代码例:

# 斐波那契
def fib(n):
    a = 0
    b = 1
    for i in range(1,n+1):
        temp = a + b
        a = b
        b = temp
        yield b  # 类似于return,返回b。但是与return不同,不会中断程序。

print(fib(10))  # 会返回一个generator的内存地址。

f = fib(10)
print(next(f))  # 与迭代器一样,执行一步。返回第一次循环的b
print(f.__next__())  # 执行第二步。 next(f) 和 f.__next__() 的效果是完全一样的。

print('====do something else...')  # 可以在函数执行中,中断函数插入别的内容

print(next(f))  # 执行第三步
print(next(f))  # 执行第四步

for i in f:  # 用这个可以把剩下的都执行完。原理和迭代器一样
    print(i)

扩展-复杂写法:

# 请使用生成器表达式将列表li = [1, 2, 3, 4, 5, 6, 7, 8, 9]中大于5和小于5的元素乘以2 
li = [1, 2, 3, 4, 5, 6, 7, 8, 9]

g = (i * 2 for i in li if i > 5 or i < 5)

5.14 用生成器来实现并发编程

  • send(self,value)
    向生成器的yield传入值。send相当于:先传入参数 -> 再next()

例:

def g_receive():
    
    while True:
        n = yield  # 这个作用是从外面接受yield的返回值。
        print('receive var is :',n)
        # 注意,生成器内包含yield循环不需要设置结束,func调用后会自动停止在最后一次调用的地方,但也不会跳出循环。
        # 如果循环次数有上限,注意send()的次数不能超过循环上限。
        # send()传入的参数也不可以作为while的结束条件,这样会发生StopIteration 错误

g = g_receive()  # 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有 执行。
g.__next__()  # 调用生成器:第一个传入生成器的值必须是None,用.__next__() 或 next(g) 或 .send(None)会发送None到yield。

# 当next方法第一次被调用的时候,生成器函数才开始执行,执行到yield语句处停止。第一次next相当于启动。
# 第一次next执行完后,我们才可以用send来传值。

for i in range(10):
    g.send(i)  # 调用生成器:传i给yield。

作用:实现单线程下的多并发效果。(线程就是cpu执行的任务单元)

  • 简单的单线程多并发例:
def output():
    count = 1
    while count <= 100:
        n = yield
        print('收到数据%s' % (n,))

g1 = output()
g2 = output()
g3 = output()

g1.__next__()
g2.__next__()
g3.__next__()

for i in range(100):
    g1.send(i+1)
    g2.send(i*2)
    g3.send(i/2)

5.15 可迭代类型和迭代器

  • 可以直接作用于for循环的数据类型(可迭代的数据类型)有以下几种:
  1. 一类是集合数据类型,如list,tuple,dict,set,str 等
  2. 一类是generator,包括生成器和带yield的generator function

这些可以直接用作于for循环的对象统称为可迭代类型:iterable,可迭代的意思就是可遍历,可循环。

可以用isinstance(o, Iterable)来判断对象是否可迭代。

Iterable在collections模块中

from collections import Iterable,Iterator  # 判断迭代器类型需要导入模块

isinstance({},Iterable)  # 判断是可迭代的。返回True

isinstance({},Iterator)  # 判断是迭代器。返回False。因为字典,列表,字符串没有next()方法。不是迭代器

isinstance((x for x in range(10)),Iterator)  # 是迭代器。返回True

iter({})  # 把可迭代对象转成迭代器,转完了就可以用next了

生成器是迭代器的一种。