Python 迭代器 和 生成器
迭代器 irerator
迭代器是一个带状态的对象,他能在你调用 next() 方法的时候返回容器中的下一个值,直到没有数据时抛出StopIteration错误,任何实现了 __next__() 方法的对象都是迭代器。
如:
it=iter(['a','b','c'])
>>> print(it)
<list_iterator object at 0x7fc946366160>
>>> it.__next__()
'a'
>>> next(it)
'b'
注意: 在交互式终端可以直接显示返回值,在写在.py文件中需要print(next(it)) 方可打印
迭代器可以表示一个无限大的数据流,可以把这个数据流看做是一个有序序列,但不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以迭代器不会占用很大内存,只有在需要返回下一个数据时它才会把数据加载进内存。比如在linux操作系统中要读取一个几十G的文件,如果用vim编辑器打开这个文件会很慢(因为需要等待vim把文件内容加载进内存),而用cat,more,less等命令可以瞬间读取文件的内容,而常常我们只需要查看文件内容的前面部分,如果把文件的后面内容也加载进内存就白白浪费了。
>>> with open('file','r') as f
>>> for i in f: # 默认以循环迭代的方式一行一行读取文件内容
print i
生成器 generator
生成器其实是一种特殊的迭代器,也是一种特殊的函数,是Python语言中最吸引人的特性之一,
即如果一个函数定义中包含yield关键字,它便是一个生成器。它可以通过next()函数或__next__()方法返回下yeild值,就像迭代器一样。
yield语句: 接收一个值并返回这个值,有两种方式接收值(不接受任何值默认返回None)
1. 直接在yield 语句后写要传递的值
2. 通过send() 方法传递给yield
例如:用生成器来实现斐波那契数列,前面两个数之和等于后一个
#!/usr/bin/env python
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b # 等同于a=b , b=a+b
n += 1
return 'done'
g=fib(10)
print(g)
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
运行结果:
<generator object fib at 0x7f1548d11d00>
1
1
2
3
生成器的执行流程:
普通函数是顺序执行,遇到return语句或者最后一行函数语句就返回。generator和普通函数的执行流程不一样,在每次调用next()的时候执行函数,遇到yield语句返回,再次执行时从上次返回的yield语句后继续执行。函数中的while循环并没有结束,在此期间可以做任何其他事,随时可以再次回到函数,这便是生成器的魅力。
为了更好地理解,我们模拟一个场景,假设去银行取款机取钱,每次只能取100元,然后拿去消费。
#!/usr/bin/env python def cash(account): print('\nWelcome to bank, you have balance: %s$' %account) n=1 while account > 0: account -= 100 print('The %s time: drawing out 100$ cash' %n) yield print('\nWelcome to bank, you have balance: %s$' %account) n+=1 else: print('Insufficient account balance.')
return 'Done' atm=cash(300) # 我们传递一个实参300,表示存入300$到银行,此时函数没有真正执行 next(atm) # next()真正开始执行函数,表示第一次在ATM 取出100$ print('buy a dress') # 取完钱后去买了一件衣服 print(next(atm)) # 再去取钱,可以看到yield 返回值为None print('eat meal ') #然后去吃了一顿大餐
next(atm) # 第三次去取钱
print('Watch movie') # 去看了一场电影
next(atm) # 第4次去取钱被告知余额不足,并抛出一个异常StopIteration,此时while循环已经结束,函数返回'Done'
运行结果: Welcome to bank, you have balance: 300$ The 1 time: drawing out 100$ cash buy a dress Welcome to bank, you have balance: 200$ The 2 time: drawing out 100$ cash None eat meal Welcome to bank, you have balance: 100$ The 3 time: drawing out 100$ cash Watch movie Welcome to bank, you have balance: 0$ Insufficient account balance. Traceback (most recent call last): File "cash.py", line 25, in <module> next(atm) StopIteration: Done
要是不想抛出异常,可以捕获这个异常,将最后一个next(atm)替换如下: try: next(atm) except StopIteration as e: print('Generator return value:', e.value)
再次执行结果:
Welcome to bank, you have balance: 0$
Insufficient account balance.
Generator return value: Done
可以看到yield语句 相当于在函数中设置了一个断点,在函数返回值前可以做任何操作, 然后再回到断点处,从yield语句后继续执行。
使用send()给yield 传值
上面的例子是一个同步或串行的过程,每次需要next()函数或__next__()方法来执行生成器。现在我们来考虑一个异步的过程,模拟一个场景:我们去一家面馆吃面,前面有很多顾客在等待,但我们点完餐以后不想在饭馆等,让服务员实时通知我们前面还有多少人在等待,这里假设厨师做一碗面的时间为10分钟。
#!/usr/bin/env python
import time def consumer(name): print('Mr %s, Welcome to xxx restaurant') while True: receive = yield #将yield返回值赋给变量,相当于接收通知的接口 if receive==0: print('Your\'s OK,enjoy it!') #如果前面等待的人数为0,通知你已经做好了。 else: print("there are %s person waiting." %receive) # 否则通知等待人数 def Notice(Num): print(c.__next__()) # 可以看到yield默认返回值为None while Num>=0: time.sleep(1) #这里用1s钟代替10分钟,方便看到效果 c.send(Num) # 使用send()给客户"发短信",通知客户当前状态信息 Num-=1 #每做好一份将等待人数减1
c=consumer('Zhou') Notice(3) #假设前面有3个人在等待 运行结果: Mr Zhou, Welcome to xxx restaurant None there are 3 person waiting. there are 2 person waiting. there are 1 person waiting. Your's OK,enjoy it !
send()给传递的值会覆盖掉yield默认值,并自动继续执行yield后面的语句,从而不用每次都用next()函数调用生成器。
使用表达式快速构造一个生成器
>>> g = (i * i for i in range(10)) >>> g <generator object <genexpr> at 0x7f4dd3ddbca8> >>> g.__next__() 0 >>> g.__next__() 1 >>> g.__next__() 4
>>> L = [i * i for i in range(10)]
>>> L
0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
注意[]()的区别,[]构造的是列表,()构造的是生成器
L = [i * i for i in range(10)]
等价于:
L=[]
for i in range(10):
a=i*i
L.append(a)
最后附上一张容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)之间的关系图(图片来源于网络)


浙公网安备 33010602011771号