源Link:http://www.cnblogs.com/huxi/archive/2011/07/01/2095931.html
迭代器
迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么, 因为人们很少在迭代途中往后退。另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件
特点:
- 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
- 不能随机访问集合中的某个值 ,只能从头到尾依次访问
- 访问到一半时不能往回退
- 便于循环比较大的数据集合,节省内存
class listiterator(object)
| Methods defined here:
|
| __getattribute__(...)
| x.__getattribute__('name') <==> x.name
|
| __iter__(...)
| x.__iter__() <==> iter(x)
|
| __length_hint__(...)
| Private method returning an estimate of len(list(it)).
|
| next(...)
| x.next() -> the next value, or raise StopIteration listiterator
生成一个迭代器:
#在list中有个__iter__方法,就是个迭代器,其类里必须有个next方法。需要通过遍历来具体输出 others = iter(['liupeng','tony','jack','rain']) #iter代表生成的是个迭代器。 print(others) #Result(下面结果为一个迭代器(list_iteratior)) <list_iterator object at 0x7efd6f8cbcc0> #others[2] # 假如我们想取'jack'这个值,如果是列表或者元组的话,通过下标就可以读取元素。但是迭代器不可以只能通过__next__()持续迭代下去到这个值才能被取出。直接用下标取值会出现一个“TypeError: 'list_iterator' object is not subscriptable”的报错。 print(others.__next__()) #迭代器一次只能迭代一个值,而且是从头开始迭代。不会重复迭代。要想读取到我们想找到的'jack',只能在第三次迭代中取出。
print(others.__next__())
print(others.__next__())
print(others.__next__())
print(others.__next__()) # 上述我们自己创建的迭代器中一共只有4个元素,如果超过迭代器元素的数量继续迭代的话,它会自动返回“StopIteration”的信息来告诉你已经到终点了。 #Result
liupeng
tony
jack
rain
Traceback (most recent call last):
File "/home/liupeng/PycharmProjects/untitled/zhengzebiaoda.py", line 80, in <module>
print(others.__next__()) # 上述我们自己创建的迭代器中一共只有4个元素,如果超过迭代器元素的数量继续迭代的话,它会自动返回“StopIteration”的信息来告诉你已经到终点了。
StopIteration
obj = iter([11,22,33,44,55,66,77,88,99,90]) # for n in obj:
print(n) #Result (利用for 循环取出iter中每个元素。)
11
22
33
44
55
66
77
88
99
90 #下面这种情况其实是抛出Stoplteration异常。事实上,Python正是根据是否检查到这个异常来决定是否停止迭代的。
#这种做法与迭代前手动检查是否越界相比各有优点。但Python的做法总有一些利用异常进行流程控制的嫌疑。
#了解了这些情况以后,我们就能使用迭代器进行遍历了。 obj = iter([11,22,33,44,55,66,77,88,99,90]) try:
while True:
val = obj.__next__()
print (val) except StopIteration:
pass #另外在使用迭代器的循环可以避开索引,但有时候我们还是需要索引来进行一些操作的。这时候内建函数enumerate就派上用场咯,它能在iter函数的结果前加上索引,以元组返回,用起来就像这样: obj =iter(['liupeng','tony','jack','rain']) for i in enumerate(obj):
print(i) #Result
(0, 'liupeng')
(1, 'tony')
(2, 'jack')
(3, 'rain')
实例1:
obj = iter([11,22,33,44,55,66,77,88,99,90]) while True:
val = obj.__next__()
print(val) #Result (while循环的话判断为真,赋一个变量。然后把每次通过obj.__next__()的结果赋值给变量打印出来。当循环超过iter中的值后条件就为Talse,那么就会报StopIteration的错误。意思就是已经没值了到底了)
22
44
66
88
90
Traceback (most recent call last):
File "/home/liupeng/PycharmProjects/untitled/zhengzebiaoda.py", line 77, in <module>
val = obj.__next__()
StopIteration
实例2:
使用迭代器一个显而易见的好处就是:每次只从对象中读取一条数据,不会造成内存的过大开销。
例如:
/* 把文件一次加载到内存中,然后逐行打印。当文件很大时,这个方法的内存开销就很大了 */
for line in open("test.txt").readlines():
print line /* 这是最简单也是运行速度最快的写法,他并没显式的读取文件,而是利用迭代器每次读取下一行 */
for line in open("test.txt"): #use file iterators
print line
生成器
定义:一个函数调用时返回一个迭代器,那这个函数就叫做生成器(generator),如果函数中包含yield语法,那这个函数就会变成生成器
- 生成器是一个特殊的程序,可以被用作控制循环的迭代行为
- 生成器类似于返回值为数组的一个函数,这个函数可以接收参数,可以被调用,但是,不同于一般的函数会一次性返回包含了所有数值的数组,生成器一次 只产生一个值,这样消耗的内粗数量大大减少,而且允许调用函数可以很快的开始处理前几个返回值。因此,生成器看起来像一个函数但是表现的却像一个迭代器。
python中的生成器
python提供了两种基本的方式。
- 生成器函数:也是用def来定义,利用关键字yield一次返回一个结果,阻塞,重新开始
- 生成器表达式:返回一个对象,这个对象只有在需要的时候才产生结果
下面详细讲解。
生成器函数
为什么叫生成器函数?因为他随着时间的推移生成了一个数值队列。一般的函数在执行完毕之后会返回一个值然后退出,但是生成器函数会自动挂起,然后重新拾起继续执行,他会利用yield关键字关起函数,给调用者返回一个值,同时保留了当前的足够多的状态,可以使函数继续执行。生成器和迭代协议是密切相关的,可迭代的对象都有一个__next()__成员方法,这个方法要么返回迭代的下一项,要么引起异常结束迭代。
为了支持迭代协议,拥有yield语句的函数被编译为生成器,这类函数被调用时返回一个生成器对象,返回的对象支持迭代接口,即成员方法__next()__继续从中断处执行执行。
看下面的例子:
def creat_counter(n):
print('create counter') while True:
yield n
print('increment n')
n += 1 cnt = creat_counter(2)
print(cnt) print(next(cnt))
print(next(cnt))
print(next(cnt))
分析一下这个例子:
- 在create_counter函数中出现了关键字yield,预示着这个函数每次只产生一个结果值,这个函数返回一个生成器(通过第一行输出可以看出来),用来产生连续的n值
- 在创造生成器实例的时候,只需要像普通函数一样调用就可以,但是这个调用却不会执行这个函数,这个可以通过输出看出来
- next()函数将生成器对象作为自己的参数,在第一次调用的时候,他执行了create_counter()函数到yield语句,返回产生的值2
- 我们重复的调用next()函数,每次他都会从上次被挂起的地方开始执行,直到再次遇到了yield关键字
为了更加深刻的理解,我们再举一个例子。
#coding
def cube(n):
for i in range(n):
yield i ** 3 for i in cube(5):
print i #output
1
27
所以从理解函数的角度出发我们可以将yield类比为return,但是功能确实完全不同,在for循环中,会自动遵循迭代规则,每次调用next()函数,所以上面的结果不难理解。
生成器表达式:
生成器表达式来自于迭代和列表解析的组合,生成器表达式和列表解析类似,但是他使用尖括号而不是方括号括起来的。如下代码:
>>> # 列表解析生成列表
>>> [ x ** 3 for x in range(5)]
[0, 1, 8, 27, 64]
>>>
>>> # 生成器表达式
>>> (x ** 3 for x in range(5))
<generator object <genexpr> at 0x000000000315F678>
>>> # 两者之间转换
>>> list(x ** 3 for x in range(5))
[0, 1, 8, 27, 64]
就操作而言,生成器表如果使用大量的next()函数会显得十分不方便,for循环会自动出发next函数,所以可以按下面方式使用:
>>> for n in (x ** 3 for x in range(5)):
print('%s, %s' % (n, n * n)) 0, 0
1, 1
8, 64
27, 729
64, 4096
两者比较
一个迭代既可以被写成生成器函数,也可以被协程生成器表达式,均支持自动和手动迭代。而且这些生成器只支持一个active迭代,也就是说生成器的迭代器就是生成器本身。
协程与yield表达式
yield语句还有更给力的功能,作为一个语句出现在赋值运算符的右边,接受一个值,或同时生成一个值并接受一个值。
def recv():
print ('Are your ready:')
while True:
n=yield
print ('总共用了 %s 秒'%n) c = recv()
c.__next__()
c.send(100)
c.send(300)
以这种方式使用yield语句的函数称为协程。在这个例子中,对于__next__的初始调用是必不可少的,这样协程才能执行可通向第一个yield表 达式的语句。在这里协程会挂起,等待相关生成器对象send()方法给它发送一个值。传递给send()的值由协程中的yield表达式返回。
协程的运行一般是无限期的,使用方法close()可以显式的关闭它。
如果yield表达式中提供了值,协程可以使用yield语句同时接收和发出返回值。
def split_line():
print('ready to split')
result = None while True:
line = yield result
result = line.split() s = split_line()
s.__next__()
print(s.send('1,2,3'))
注意:理解这个例子中的先后顺序非常重要。首个next()方法让协程执行到yield result,这将返回result的值None。在接下来的send()调用中,接收到的值被放到line中并拆分到result中。send()方法 的返回值就是下一条yield语句的值。也就是说,send()方法可以将一个值传递给yield表达式,但是其返回值来自下一个yield表达式,而不 是接收send()传递的值的yield表达式。
如果你想用send()方法来开启协程的执行,必须先send一个None值,因为这时候是没有yield语句来接受值的,否则就会抛出异常。
>>> s=split_line()
>>> s.send('1 2 3')
TypeError: can't send non-None value to a just-started generator
>>> s=split_line()
>>> s.send(None)
ready to split
使用生成器与协程
乍看之下,如何使用生成器和协程解决实际问题似乎并不明显。但在解决系统、网络和分布式计算方面的某些问题时,生成器和协程特别有用。实际上,yield已经成为Python最强大的关键字之一。
比如,要建立一个处理文件的管道:
import os,sys
def default_next(func):
def start(*args,**kwargs):
f=func(*args,**kwargs)
f.__next__()
return f
return start
@default_next
def find_files(target):
topdir=yield
while True:
for path,dirname,filelist in os.walk(topdir):
for filename in filelist:
target.send(os.path.join(path,filename)) @default_next
def opener(target):
while True:
name=yield
f=open(name)
target.send(f) @default_next
def catch(target):
while True:
f=yield
for line in f:
target.send(line) @default_next
def printer():
while True:
line=yield
print line
然后将这些协程连接起来,就可以创建一个数据流处理管道了:
finder=find_files(opener(catch(printer())))
finder.send(toppath)
程序的执行完全由将数据发送到第一个协程find_files()中来驱动,协程管道会永远保持活动状态,直到它显式的调用close()。
总之,生成器的功能非常强大。协程可以用于实现某种形式的并发。在某些类型的应用程序中,可以用一个任务调度器和一些生成器或协程实现协作式用户空 间多线程,即greenlet。yield的威力将在协程,协同式多任务处理(cooperative multitasking),以及异步IO中得到真正的体现。
有的时候真的是“眼观千遍,不如手动一遍”来的记忆犹新。
此文截取多篇博客中的案例结合而制,同时以上代码都已经验证过有效。
“今天的努力都是明天别人对你的膜拜,今天的停滞就是明天别人对你的唾弃!“