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)之间的关系图(图片来源于网络)

 

posted @ 2017-05-30 20:16  bobo0609  Views(169)  Comments(0Edit  收藏  举报