您的位置:首页 > 编程语言 > Python开发

4 Python 迭代器_生成器_列表解析

2016-09-28 20:21 639 查看

迭代器,生成器,列表解析

转载请标明出处(http://blog.csdn.net/lis_12/article/details/52693507)

迭代器

定义:

任何具有next()方法的对象都是迭代器,对迭代器调用next()方法可以获取下一个值;

迭代器本质上就是一个产生值的工厂,每次调用next()方法,迭代器都会计算出相对应的值然后返回;

迭代器内部状态保存在当前实例对象的属性中,在下一次调用的时候使用上次保存的属性;

调用next()方法都会执行下面两步:

1) 修改属性,以便下次调用next()方法;

2) 计算当前的返回结果;

比喻:从外部来看,迭代器就像警察一样,有案子的时候,警察就会忙,没有案子,警察就会忙,将结果查出来后,如果没事了,又会继续闲着.

迭代器可迭代多次,因为他们全部都在内存里,如果数据量太大会耗费很多内存,如很大的文件;

特点:

迭代器不是通过索引访问值得;

迭代器不能回退,只能往前进行迭代;

迭代器也不是线程安全的;

在多线程环境中对可变集合使用迭代器是一个危险的操作.但如果小心谨慎,或者干脆贯彻函数式思想坚持使用不可变的集合,那这也不是什么大问题.

对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值).但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式;

迭代器的另一个优点就是它不要求你事先准备好整个迭代过程中所有的元素;

迭代器仅仅在迭代至某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁.这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等.这个特点被称为延迟计算或惰性求值(Lazy evaluation);

序列,字典,文件中当使用for x in y的结构时,其实质就是迭代器,迭代器是和实际对象绑定在一起的,所以在使用迭代器时或者上述3者时不能修改可变对象的值.这会产生错误.如:在使用for x in y的结构来遍历字典时删除符合条件的字典内容,这会导致报错;

创建迭代器的方法:iter(object)和iter(func,sentinel)两种.一种使用的是序列,另一种使用类来创建;

迭代器为有next方法的对象数据取完的时候会触发一个异常;

迭代器不能复制;

可迭代对象与迭代器

可迭代对象可以为任意对象,如list,tuple,只要这个对象可以返回一个iterator,该迭代对象就可以变成一个迭代器;

迭代是可迭代对象(对应
__iter__()
方法)和迭代器(对应
__next__()
方法)的一个过程.可迭代对象就是任何你可以迭代的对象(废话啊).迭代器就是可以让你迭代可迭代对象的对象(有点绕口,意思就是这个意思);

l = [1,2,3]  #可迭代对象
t = iter(l)  #迭代器
print type(l)#list
print type(t)#list_iterator
'''
最大的区别,迭代器可以调用next()方法,而可迭代对象需要先转成迭代器才能调用next方法
'''


code

for ... in ...:
dosomething()
'''
可以用在for...in...语句中的都是可迭代的:比如lists,strings,files...因为这些可迭代的对象你可以随意的读取所以非常方便易用,但是你必须把它们的值放到内存里,当它们有很多值时就会消耗太多的内存.
'''


def fun():
'''迭代器使用 any all,迭代器不可复制的哦,谨记'''
a = iter(range(10))
c = a
print (c is a)         #True
b = reversed(range(10,20))
print 'a',type(a),a    #a <type 'listiterator'> <listiterator object at 0x0000000002EDB7B8>
print 'a.next',a.next()#0
print 'c.next',c.next()#1
print (c is a)         #True
print 'a.next',a.next()#2
print 'c.next',c.next()#3
print 'b',type(b),b    #b <type 'enumerate'> <enumerate object at 0x00000000029F9A20>
print 'b.next',b.next()#(0,10)
while True:
print a.next()     # 4,5,....9,StopIteration


explain:

首先要将list->迭代器才能调用next()方法;

因为迭代器不可复制,所以将a赋给了c,c相当于a的一个别名,对a操作也就是对c操作,对c操作也是对a操作;<有点绕,自行理解–!>;

is 判断的是地址是否相等,==号判断的是值是否相等;

当迭代对象的数据取完的时候,会触发一个StopIteration的异常,for循环在操作可迭代对象的时候就是通过检查这个异常来判断是否停止循环的;

类,迭代器

一个类里面实现了__iter__和next()方法就可以作为迭代器使用;

__iter__仅返回self,这就是将一个对象声明为迭代器的方式;

next() 获取值;

code

class Randseq(object):
def __init__(self, num,seq):
self._times = num
self._data = seq

def __iter__(self):
return self

def next(self):
print 'times',self._times
self._times -= 1

if self._times < 0:
raise(StopIteration)

return self._data[self._times]

if __name__ == '__main__':
a = Randseq(10,['a','b','c','d','e','f','g','m','x','y'])
#for会将a转换成迭代器并且调用next(),同对list操作一样
for i in a:
print 'i:',i
result:
times 10
i: y
times 9
i: x
times 8
i: m
times 7
i: g
times 6
i: f
times 5
i: e
times 4
i: d
times 3
i: c
times 2
i: b
times 1
i: a
times 0


列表解析

主要用来动态的创建列表,和map()、filter()和reduce()一样可以用来产生列表.和生成器不同的是,列表解析一次生成一个列表,所占内存较大;

列表解析的扩展版本语法:

[expr for iter_var1 in iterable if cond_expr1 ... for iter_varn in iterable if cond_exprn]


和以下代码基本等价.

s = []
for iter_var1 in iterable:
if cond_expr1:
...
for iter_varn in iterable:
if cond_exprn:
s.append(expr)


返回list;

Ps:以下都是需要调用可迭代对象

filter(function,iterable) <过滤数据> 返回表达式为真的值;

map(function, iterable, …) <对每个数据操作> 对每个值进行调用function函数;(不影响iterable中的值,生成一个新对象);

reduce(function,iterable) 将二元函数作用于seq的元素,连续的将现有的结果和下一个值作用在随后的结果上,function里面多少个参数就可放多少个可迭代对象;

经典问题

因为对元素应用的动作太复杂,不能用一个表达式写出来,所以不使用列表解析.这是典型的思想没有转变的例子,如果我们将动作封装成函数,那不就是一个表达式了么…….

因为if子句里的条件需要计算,同时结果也需要进行同样的计算,不希望计算两遍.

(x.doSomething() for x in lst if x.doSomething()>0)

#优化版

tmp = [x.doSomething() for x in lst]
(x for i in tmp if i > 0)

#最终版

(x for i in (y.dosomething for y in lst) if x > 0)


code

#!/usr/bin/python
# -*- coding: utf-8 -*-
add_lambda=lambda x,y: x+y

def filter_map():
def add(x,y):
if isinstance(x,int) and isinstance(y,int):
return x+y

a = range(10)
add = lambda x,y:x+y
flag = lambda x:x%2
divide = lambda x:x>>1

print [i for i in a if i%3==0]   # [0,3,6,9]
print filter(flag,a)             # [1,3,5,7,9]
print map(add,a,a)               # [0,2,4,6,8,10,12,14,16,18]
print map(divide,a)              # [0,0,1,1,2,2,3,3,4,4]

#计算a的和
print reduce(add,range(5))       # 10
#等价于f(f(f(0,1),2),3).....
print zip(range(4),range(5,8))   # [(0, 5), (1, 6), (2, 7)]

#多个表达式嵌套
a = range(4)
print [(i,j) for i in a if i%2 == 0 for j in a if j%2] #[(0, 1), (0, 3), (2, 1), (2, 3)]


生成器

定义

生成器其实就是一种特殊的迭代器(迭代器需要注意的事项,生成器也需要注意哦- -),它提供了非常便利的自定义迭代器的途径;

生成器只能迭代一次,原因很简单,因为他们不是全部都在内存里,他们只在要调用的时候才会在内存中;

任意生成器都是一个可以延迟创建值的工厂;

挂起并返回中间值(yield 后面的值)并多次继续的协同程序被称为生成器,<简单来说是带yield语句的函数,到结尾的时候跑出异常>;

特点:

yield指令,暂停一个函数,并且返回中间结果(和return效果差不多,多了个暂停功能,待下次调用继续从暂停处运行).使用该指令的函数可以保存执行环境,并且在必要时恢复;

生成器是一种特定的函数,允许先返回一个值,然后“暂停”代码的执行,稍后恢复;

生成器使用了“延迟计算”,所以在内存上面更加有效;

生成器函数在每次暂停执行时,函数体内的所有变量都将被封存(freeze)在生成器中,并将在恢复执行时还原,并且类似于闭包,即使是同一个生成器函数返回的生成器,封存的变量也是互相独立的;

生成器函数,生成器表达式

包含yield的函数就是生成器函数;

生成器表达式(与列表解析式的区别是[]());

(expr for iter_var in iterable if cond_expr)


((x,y) for x in range(3) for y in range(3))


code

def get():
yield 0
yield 1
yield 2
print get        #<function get at 0x00B2CB70>
#上述为函数类型,与一般函数不同的是get函数体内使用了关键字yield,这就使得get函数成为了一个生成器函数

g = get()
print g          #<generator object get at 0x00B1C7D8> 生成器对象

'''
因为生成器也为迭代器的一种,所以可以调用next方法.
第一次调用生成器的next方法时,生成器才可以执行生成器函数(不是构造哦),直到遇到yield时暂停执行(也可以叫挂起),并且  yield的参数作为此次next方法的返回值 .
'''
print g.next()   #0
print g.next()   #1
print g.next()   #2
print g.next()   #没有yield的时候或遇到return,抛出StopIteration异常,生成器函数结束

def fun():
for i in range(10):
print 'stop:',i
yield 'time:',ctime(),' i:',i
a = fun() #generator object fun at 0x00000000024FF1F8
print a.next()  #返回的是yield后面带的值
print a.next()
print type(a.next())  #tuple
result:
stop: 0
('time:', 'Thu Sep 08 09:26:35 2016', ' i:', 0)
stop: 1
('time:', 'Thu Sep 08 09:26:35 2016', ' i:', 1)


#延迟计算 这个例子中我们定义了一个生成器用于获取斐波那契数列.
def fibonacci():
a = b = 1
yield a
yield b
while True:
a, b = b, a+b
yield b

for num in fibonacci():
if num > 100:
break
print num

#result:1 1 2 3 5 8 13 21 34 55 89
#看到while True可别太吃惊,因为生成器可以挂起,保存运行环境,暂停运行,所以是延迟计算的,无限循环并没有关系.


生成器注意的问题

生成器函数是可以带参数的;

生成器虽然同为函数,但是不能使用return返回值;

因为生成器函数已经有默认的返回值(生成器)了,so不能在给另外的返回值了,即使是None也不行,但是可以使用一个空的return来结束;

生成器在迭代过程中接入了另一个生成器的迭代器,这样是不行的哦..

协同程序

特征:

彼此间有不同的局部变量,指令指针,但仍共享全局变量;

可以方便地挂起,恢复,并且有多个入口点和出口点;

多个协同程序间表现为协作运行,如A的运行过程中需要B的结果才能继续执行.

协程的特点决定了同一时刻只能有一个协同程序正在运行(忽略多线程的情况).得益于此,协程间可以直接传递对象而不需要考虑资源锁,或是直接唤醒其他协程而不需要主动休眠,就像是内置了锁的线程.在符合协程特点的应用场景,使用协程无疑比使用线程要更方便.

从另一方面说,协程无法并发其实也将它的应用场景限制在了一个很狭窄的范围,这个特点使得协程更多的被拿来与常规函数进行比较,而不是与线程.当然,线程比协程复杂许多,功能也更强大,所以我建议大家牢牢地掌握线程即可:Python线程指南

生成器中加入了协程

方法如下:

1 send(value)

返回值,return next yielded value or raise StopIteration**,返回下一个yield的值**

send是除next外另一个恢复生成器的方法,调用了send方法后,生成器则从send(value)中value的值开始迭代.

调用send传入非None值前,生成器必须处于挂起状态,否则将抛出异常.

未启动的生成器仍可以使用None作为参数调用send.

2 close()

这个方法用于关闭生成器.对关闭的生成器后再次调用next或send将抛出StopIteration异常.

3 throw(type, value=None, traceback=None):

这个方法用于在生成器内部(生成器的当前挂起处,或未启动时在定义处)抛出一个异常.

code

next()

def f(arg = 0):
while True:
rec = yield arg
print 'rec:',rec," type:",type(rec)
if rec == None:
arg += 1
else:
arg = rec
g = f()
for _ in range(5):
print g.next()
print '- - - - - - '
result:
0
- - - - - -
rec: None  type: <type 'NoneType'>
1
- - - - - -
rec: None  type: <type 'NoneType'>
2
- - - - - -
rec: None  type: <type 'NoneType'>
3
- - - - - -
rec: None  type: <type 'NoneType'>
4
- - - - - -


解析:

循环argrecresult
00执行至yield arg处暂停,此时返回yield后的arg,打印0,(此时并不会执行接收语句处= =,即rec = yield arg,只执行至yield arg处,并没有赋值)
11None从上次yield arg(0)处开始执行,因为rec没有接收到值,所以此时rec的值为None;执行打印rec语句,并且因为rec = None,所以arg+1,so arg = 1,再次执行至yield arg(1)处,返回1
22None从上次yield arg(1)处开始执行,因为rec没有接收到值,所以此时rec的值为None;执行打印rec语句,并且因为rec = None,所以arg+1,so arg = 2,再次执行至yield arg(2)处,返回2
33None…….

send()

def f(arg = 0):
i = 1
while True:
rec = yield arg
print 'rec:',rec," type:",type(rec)

if rec == None:
arg += 1
else:
arg = rec + i
i += 1

g = f()
for i in range(5):
if i%2 == 1:
print 'send:%d'%g.send(i)
else:
print 'next:%d'%g.next()
print '- - - - - - '
result:
next:0
- - - - - -
rec: 1  type: <type 'int'>
send:2
- - - - - -
rec: None  type: <type 'NoneType'>
next:3
- - - - - -
rec: 3  type: <type 'int'>
send:5
- - - - - -
rec: None  type: <type 'NoneType'>
next:6
- - - - - -


解析:

循环iargrecresult
0 next()10执行至yield arg(0)处暂停,此时返回yield后的arg(0),打印0,(此时并不会执行赋值语句,即rec = yield arg,只执行至yield arg处,并没有赋值);
1 send(1)221从上次yield arg(0)处开始执行,此时rec接收到生成器发送来的值,rec = 1;执行打印rec语句,并且因为rec != None,arg = rec(1) + i(1),再次执行至yield arg(2) 处,返回2;
2 next()23None从上次yield arg(2)处开始执行,因为rec没有接收到值,所以此时rec的值为None;执行打印rec语句,并且因为rec = None,so arg = 3,再次执行至yield arg(3)处,返回3;
3 send(3)353从上次yield arg(3)处开始执行,此时rec接收到生成器发送来的值,rec = 3;执行打印rec语句,并且因为rec != None,arg = rec + i(2),再次执行至yield arg 处,此时arg = 5,返回5;
4 next()36None从上次yield arg(5)处开始执行,因为rec没有接收到值,所以此时rec的值为None;执行打印rec语句,并且因为rec = None,so arg = 6,再次执行至yield arg(6)处,返回6
本段代码总结:

生成器每次都在yield处暂停,不再执行任何东西了,包括赋值等操作,等到下一次迭代再说;

next()函数等价于send(None);

send函数的返回值和next一样,也是yield后面的值;

close()

def f(arg = 0):
while True:
yield arg
arg += 1

g = f()
print g.next()  #0
print g.next()  #1
g.close()       #关闭生成器
print g.next()  #StopIteration


throw

def f():
try:
while True:
yield 1
except Exception:
yield 'Exception'
finally:
yield 'finally'

g =f()
print g.next()  #1
print g.next()  #1
throw(Exception)#Exception RuntimeError: 'generator ignored GeneratorExit' in <generator object f at 0x0000000002496828> ignored


def f():
try:
yield 1
except Exception:
yield 'Exception'
finally:
yield 'finally'

g =f()
print g.next()  #1
print g.next()  #finally
print g.next()  #StopIteration


类,生成器

感觉和迭代器写法差不多啊- -!

class Randseq(object):
'''生成器测试'''
def __init__(self, num,seq):
self._times = num
self._data = seq

def __iter__(self):
'''将一个对象声明为迭代器的方式'''
return self

def next(self):
print 'times',self._times
self._times -= 1

if self._times < 0:
raise(StopIteration)
yield self._data[self._times]

a = Randseq1(10,['a','b','c','d','e','f','g','m','x','y'])
for i in a:
print 'i:%s'%i.next()
result:
times 10
i:y
times 9
i:x
times 8
i:m
times 7
i:g
times 6
i:f
times 5
i:e
times 4
i:d
times 3
i:c
times 2
i:b
times 1
i:a
times 0


看了几天,写了几天终于写完了- -,可能理解的不够透彻,如有错误请及时指正,fighting……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息