6.迭代器
特殊方法__iter__,是迭代器规则的基础
6.1迭代器规则
迭代的意思是重复做一些事很多次,就像在循环中做的那样,但现在为止只是在for循环中对序列和字典进行迭代,但实际上也能对其他对象进行迭代,实现__iter__方法的对象,
__iter__方法返回一个迭代器,所谓的迭代器就是具有next方法(这个方法在调用时不需要任何参数)的对象,在调用next方法时,迭代器会返回它的下一个值,如果next方法被调用,但迭代器没有值可以返回,就会引发一个StopIteration异常
迭代规则的关键是什么?如果可以一个接一个地计算值的函数,那么在使用时可能是计算一个值时获取一个值,而不是通过列表一次性获取所有值,如果有很多值,列表就会占用太多的内存,还有其他理由:使用迭代器更通用,简单优雅,
迭代器实现斐波那契数列
Class Fibs:
Def __init__(self):
Self.a = 0
Self.b = 0
Def next(self):
Self.a, self.b = slef.b, self.a + self.b
Return self.a
Def __iter__(self):
Return self
迭代器实现了__iter__方法,这个方法实际上返回迭代器本身,在很多情况下,__iter__回放到其他的会在for循环中使用的对象中,这样一来,程序就能返回所需的迭代器,此外,推荐使用迭代器实现它自己的__iter__方法,然后就能直接在for循环中使用迭代器本身了
一个实现了__iter__方法的对象是可迭代的,一个实现了next方法的对象则是迭代器
首先,产生一个Fibs对象
>>> fibs = Fibs()
可在for循环中使用该对象---比如去查找在斐波那契数列中比1000大的数中的最小的数
>>> for f in fibs:
If f > 1000:
Print f
Break
.....
1597
因为设置了break,所以循环在这里停止了,否则会一直继续下去
内建函数iter可以从可迭代的对象中获得迭代器
>>> it = iter([1, 2, 3])
>>> it.next() 1
>>> it.next() 2
6.2 从迭代器得到序列
除了在迭代器和可迭代对象上进行迭代,还能把它们转换为序列,在大部分能使用序列的情况下(除在索引或分片等操作中),能使用迭代器(或可迭代对象)替换,关于这个一个很有用的例子是使用list构造方法显式地将迭代器转化为列表
>>> class TestIterator:
value = 0
def next(self):
self.value += 1
if self.value > 10: raise StopIteration
return self.value
def __iter__(self):
return self
>>> t1 = TestIterator()
>>> list(t1) [1,2,3,4,5,6,7,8,9,10]
7.生成器
也叫简单生成器,是一种用普通的函数语法定义的迭代器
7.1创建生成器
首先创建一个展开嵌套列表的函数,参数是一个列表,
Nested = [[1,2], [3, 4], [5]]
换句话说就是一个列表的列表,函数应该按照顺序打印出列表中的数字
Def flatten(nested):
For sublist in nested:
For element in sublist:
Yield element
首先迭代提供的嵌套列表中的所有自列表,然后按顺序迭代子列表中的元素
如果最后一行是print Element则容易理解了
包含yield语句的函数成为生成器,吹了名字不同,他的行为和普通函数也差很大
它不是像return那样返回值,而是每次产生多个值,每次产生一个值函数就会被冻结,即函数停在那点等待被激活,函数被激活后就从停止点开始执行
接下来可以通过在生成器上迭代来使用所有的值
>>> nested = [[1, 2], [3, 4], [5]]
>>> for num in flatten(nested)
Print num
1
2
3
4
5
or
>>> list(flatten(nested))
[1,2,3,4,5]
7.2 递归生成器
任意层第嵌套
Def flatten(nested):
Try:
For sublist in nested:
For element in flatten(sublist):
Yield element
Except TypeError:
Yield nested
当flatten被调用时,有两种可能性(处理递归时大部分都是两种情况):基本情况和需要递归的情况,在基本情况中,函数被告知展开一个元素(比如一个数字),这种情况下,for循环会引发一个TypeError异常(因为试图对一个数字进行迭代),生成器会产生一个元素。
如果展开的是一个列表(或其他可迭代对象),那么就要进行特殊处理,程序必须遍历所有子列表(一些可能不是列表),并对他们调用flatten,然后使用另一个for循环来产生被展开的子列表中的所有元素,
>>> list(flatten([[1,2],3,4,[5,[6,7],8]))
[1,2,3,4,5,6,7,8]
这么做只有一个问题,如果nested是一个类似于字符串的对象(字符串,Unicode,UserString,等)那么她就是一个序列,不会引发TypeError
不应该在flatten函数中对类似于字符串的对象进行迭代,出于两个主要原因,首先需要实现的是将类似于字符串的对象当成原子值,而不是当成应被展开的序列,其次对它们进行迭代实际上会导致无穷递归,因为一个字符串的第一个元素是另一个长度为1的字符串,而长度为1的字符串的第一个元素就是字符串本身
为了处理这种情况,则必须在生成器的开始处添加一个检查语句,将传入的对象和一个字符串拼接,看看会不会出现TypeError,这是检查一个对象是不是类似于字符串的最简单最快速的方法
def flatten(nested):
try:
try: nested + ‘’
except TypeError: pass
else: raise TypeError
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
如果表达式nested+’’引发了一个TypeError,它就会被忽略,然而如果没有引发TypeError,那么内存try语句中的else子句就会引发一个它自己的TypeError异常。这就会按照原来的样子生成类似于字符串的对象(在except字句的外面)
>>> list(flatten([‘foo’, [‘bar’, [‘baz’]]]))
[‘foo’, ‘bar’, ‘baz’]
上面的代码没有执行类型检查,这里没有测试nested是否是一个字符串(可以使用ininstance函数完成检查),而只是检查nested的行为是不是像一个字符串(通过和字符串拼接来检查)
7.3通用生成器
生成器是一个包含yield关键字的函数,当它被调用时,在函数体中的代码不会被执行,而会返回一个迭代器,每次请求一个值,就会执行生成器中的代码,直到遇到一个yield或者return语句,yield语句意味着应该生成一个值,return语句意味着生成器要停止执行(不再生成任何东西,return语句只在一个生成器中使用时才能进行无参数调用)
生成器由两部分组成:生成器的函数和生成器的迭代器,生成器的函数时用def语句定义的,包含yield的部分,生成器的迭代器是这个函数返回的部分,按照一钟不是很准确的说法,两个实体经常被当做一个,合起来叫做生成器
>>> def simple_generator():
yield
>>> simple_generator
<function simple_generator at 153b44>
>>> simple_generator()
<generator object at 1510b0>
生成器的函数返回的迭代器可以像其他的迭代器那样使用
7.4生成器方法
生成器的新属性是在开始运行后位生成器提供值的能力,表现为生成器和‘外部世界’进行交流的渠道
外部作用域访问生成器的send方法,就像访问next方法一样,只不过前者使用一个参数(要发送的‘消息’--任意对象)
在内部则挂起生成器,yield现在作为表达式而不是语句使用,换句话说,当生成器重新运行的时候,yield方法返回一个值,也就是外部通过send方法发送的值,如果next方法被使用,那么yield方法返回None
使用send方法只有在生成器挂起后才有意义(也就是yield函数第一次被执行之后),如果在此之前需要给生成器提供更多信息,只需使用生成器函数的参数
如果真想对刚刚启动的生成器使用send方法,可以将None作为参数进行调用