迭代器
迭代器规则
迭代:重复做一些事很多次,就像在循环中那样。
不仅可以对字典和序列进行迭代,还可以对其他对象进行迭代:只要该对象实现了__iter__方法。
__iter__方法会返回一个迭代器(iterator),所谓的迭代器就是具有next方法(这个方法在调用时不需要任何参数)的对象。在调用next方法时,迭代器会返回他的下一个值。如果next方法被调用,但迭代器没有值可以返回,就会引发一个StopIteration异常。
注意:迭代器规则在3.0中有一些变化。在新的规则中,迭代器对象应该实现__next__方法,而不是next。而新的内建函数next方法可以用于访问这个方法。换句话说,next(it)等同于3.0之前版本中的it.next()。
迭代规则的关键是什么?为什么不使用列表呢?因为列表的杀伤力太大了。如果有一个函数,可以一个接一个地计算值,那么在使用时可能是计算一个值时获取一个值——而不是一次性获取所有值。如果有很多值,列表就会占用太多的内存。但还有其他的理由:使用迭代器更通用,更简单,更优雅。让我们看看一个不使用列表的例子,因为要用的话,列表长度必须无限。
这里的“列表”是一个斐波那契数列。使用的迭代器如下:
class Fibs: def __init__(self): self.a = 0 self.b = 1 def __next__(self): self.a, self.b = self.b, self.a+self.b return self.a def __iter__(self): return self fibs = Fibs() # 创建一个斐波那契数列对象 for f in fibs: # 对数列进行迭代 if f > 100: print(f) break
注意:迭代器实现了__iter__方法,这个方法实际上返回迭代器本身。在很多情况下,__iter__会放到其他的会在for循环中使用的对象中。这样一来,程序就能返回所需要的迭代器。此外,推荐使用迭代器实现它自己的__iter__方法,然后就能直接在函数中使用迭代器本身了。
**正式的说话是,一个实现了__iter__方法的对象是可迭代的,一个实现了next方法的对象则是迭代器。
**内建函数iter可以从可迭代的对象中获得迭代器。此外,他也可以从函数或者其他可调用的对象中获取可迭代对象。
从迭代器得到序列
除了在迭代器和可迭代对象上进行迭代(这是经常做的)外,还能把它们转换为序列。在大部分能使用序列的情况下(除了在索引或者分片等操作中),都能使用迭代器(或者可迭代对象)替换。关于这个的的一个很有用的例子是使用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 ti = TestIterator() list(ti)
生成器
生成器是一种普通的函数定义的迭代器。
1 def flatten(nested): 2 for sublist in nested: 3 for element in sublist: 4 yield element 5 6 nested = [[1, 2], [3, 4], [5]] 7 for num in flatten(nested): 8 print(num) 9 10 -----结果 11 D:Python27python.exe D:/pythonwork/day04/iterator.py 12 1 13 2 14 3 15 4 16 5
任何含有yield语句的函数都称为生成器。出名字不同以外,他的行为和普通的函数也是有很大的差别的。这就是它不是像return那样返回值,而是每次产生多个值。每次产生一个值(使用yield语句),函数就会被冻结:即函数停在那点等待被重新唤醒。函数唤醒后就从停止的那点开始执行。
递归生成器
要解决多层嵌套循环的问题,需要借助于递归(recursion)。如下:
1 # 用递归解决多次嵌套循环问题 2 3 4 def flatten(nested): 5 # 处理异常 6 try: 7 for sublist in nested: 8 for element in flatten(sublist): 9 yield element # 使用yield每次产生一个值,之后函数会被冻结,等待唤醒。 10 except TypeError: 11 yield nested
解析上述案例:
当flatten被调用时,有两种可能性(处理递归时大部分都是有两种情况):基本情况和需要递归的情况。在基本情况下,函数被告知展开一个元素(比如一个数字)。这种情况下,for循环会引发一个TypeError异常(因为试图对一个数字进行迭代),生成器会产生一个元素。
如果展开的是一个列表(或者其他的可迭代对象),那么就要进行特殊处理。程序必须遍历所有的子列表(一些可能不是列表),并对它们调用flatten函数。然后使用另外一个for循环来产生被展开的自列表的所有元素。
*不应该flatten函数中对类似于字符串的对象进行迭代。处于两个原因。首先。需要实现的是将类似于字符串的对象当作原子值,而不是当成应被展开的序列。其次,对他们进行迭代,实际上会导致无穷递归,因为字符串的第一个元素是另外一个长度为1的字符串,而长度为1的字符串的第一个元素是其本身。
为了处理这种情况,则必须在生成器开始处添加一个检查语句。试着将传入的对象和一个字符串拼接,看会不会出现TypeError,这是检查一个对象是不是类似于字符串的最简单,最快速的方法。如下代码(加入了检查语句):
1 def flatten(nested): 2 # 处理异常 3 try: 4 try: nested + '' 5 except TypeError: pass 6 else: raise TypeError 7 for sublist in nested: 8 for element in flatten(sublist): 9 yield element # 使用yield每次产生一个值,之后函数会被冻结,等待唤醒。 10 except TypeError: 11 yield nested
对代码进行解析:
如果表达式nested+'' 引发了一个TypeError,就会被忽略。然而如果没有引发TypeError,那么内层try语句中的else子句就会引发一个它自己的TypeError异常。这样就会按照原来的样子生成类似于字符串的对象(在except子句外面)。
通用生成器
生成器就是一种包含yield关键字的函数。当他被调用时,在函数体中的代码不会被执行,而会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,知道遇到一个yield或者return语句。yield语句以为着应该生成一个值。return语句意味着生成器要停止执行(不再生成任何东西,return语句只有一个生成器中使用时才能进行无参数调用)。
生成器是由两位部分组成:生成器的函数和生成器的迭代器。生成器的函数是用def语句定义的,包含yield的部分,生成器的迭代器是这个函数返回的部分。生成器的函数返回的迭代器可以像其他的迭代器那样使用。
生成器方法
send方法:
“外部世界”作用域访问生成器的send方法,就像访问next方法一样,只不过前者使用一个参数(要发送的额“信息”——任意对象)。
yield方法:
在内部则挂起生成器,yield现在作为表达式而不是语句使用,换句话说,当生成器重新运行时,yield方法返回一个值,也就是外部通过send方法发送的值。
注意:
使用send方法(而不是next方法)只有在生成器挂起后才有意义(也就是说在yield函数第一次被执行后)。如果在此之前需要给生成器提供更多的信息,那么只需要使用生成器函数的参数。例如:
1 def repeater(value): 2 while True: 3 new = (yield value) 4 if new is not None: 5 value = new 6 7 8 r = repeater(43) 9 print(r.__next__()) 10 print(r.send("hello python!"))
生成器还有两个其他的方法
throw方法:使用异常类型调用,还有可选的值以及回溯对象,用于在生成器内引发一个异常(在yield表达式中)。
close方法:用于停止生成器。
模拟生成器
例子如下:
1 # 下面是flatten生成器用普通函数实现 2 def flatten(nested): 3 result = [] 4 try: 5 # 不要迭代类似字符串的对象 6 try: 7 nested + '' 8 except TypeError: pass 9 else: raise TypeError 10 for sublist in nested: 11 for element in flatten(sublist): 12 result.append(element) 13 except TypeError: 14 result.append(nested) 15 return result
装饰器
见武sir博客:http://www.cnblogs.com/wupeiqi/articles/4943406.html