python中的循环和迭代

在Python语法中如果用for-in循环,那么就要使用迭代器协议,只要对象支持__iter__和__next__双下划线方法,那么就能够使用for-in循环。

 1 class RepeaterIterator:
 2     def __init__(self, source):
 3         self.source = source
 4 
 5     def __next__(self):
 6         return self.source.value
 7 
 8 
 9 class Repeater:
10     def __init__(self, value):
11         self.value = value
12 
13     def __iter__(self):
14         return RepeaterIterator(self)
15 
16 
17 repeater = Repeater('Hello')
18 for item in repeater:
19     print(item)

上面的代码会在控制台循环打印"hello".

上面的for-in那一段代码可以展开成下面的但是效果相同的代码:

1 repeater=Repeater('Hello')
2 iterator = repeater.__iter__()
3 while True:
4     item = iterator.__next__()
5     print(item)

从上面可以看出,for-in只是简单的while循环的语法糖。

  • 首先让repeater对象准备迭代,即调用__iter__方法来返回实际的迭代器对象。
  • 然后循环反复调用迭代器对象的__next__方法,从中获取值。

看下面的例子,__iter__方法和__next__方法在同一个类中,

 1 class BoundedRepeater:
 2     def __init__(self, value, max_repeats):
 3         self.value = value
 4         self.max_repeats = max_repeats
 5         self.count = 0
 6 
 7     def __iter__(self):
 8         return self
 9 
10     def __next__(self):
11         if self.count >= self.max_repeats:
12             raise StopIteration
13         self.count += 1
14         return self.value
15 
16 
17 repeater = BoundedRepeater('Hello', 3)
18 for item in repeater:
19     print(item)

如果重写上面的for-in循环的例子,移除一些语法糖,那么展开后最终就会得到下面的代码片段:

1 repeater = BoundedRepeater('Hello', 3)
2 iterator = iter(repeater)
3 while True:
4     try:
5         item = next(iterator)
6     except StopIteration:
7         break
8     print(item)

每次在此循环中调用next()时都会检查StopIteration异常,并且在必要时中断while循环。

总结:

为了支持迭代,对象需要通过提供__iter__和__next__双下划线方法来实现迭代器协议。

基于类的迭代器只是用Python编写可迭代对象的一种方法。可迭代对象还包括生成器和生成器表达式。

生成器是简化版迭代器

 关键要点

  • 生成器函数是一种语法糖,用于编写支持迭代器协议的对象,与编写基于类的迭代器相比,生成器能够抽象出许多样板代码。
  • yield语句用来暂时中止执行生成器函数并传回值
  • 在生成器中,控制流通过非yield语句离开会抛出StopInteration异常。
1 class Repeater:
2     def __init__(self, value):
3         self.value = value
4         
5     def __iter__(self):
6         return self
7     
8     def __next__(self):
9         return self.value

将上面的Repeater迭代器类重写为生成器类,代码如下:

1 def repeater(value):
2     while True:
3         yield value

从中可以看出,生成器看起来像普通函数,但是它没有return语句,而是用yield将数据传回给调用者。

这个新的生成器能够像基于类的迭代器一样工作,用for-in循环测试一下:

1 for x in repeater('Hi'):
2     print(x)

 

 

 生成器看起来像是普通函数,但是行为确完全不同。调用生成器函数并不会运行该函数,仅仅创建并返回一个生成器对象:

print(repeater('Hey'))

 

 

 只有在对生成器对象上调用next()时才会执行生成器函数中的代码:

1 generator_obj = repeater('Hey')
2 print(next(generator_obj))

 

 

 yield关键字像是在某种程度上停止这个生成器函数的执行,然后在稍后的时间点恢复,当函数内部调用return语句时,控制权会永久性的交还给函数的调用者。在调用yield时,虽然控制权也是交还给函数的调用者,但是却只是暂时的。

return语句会丢弃函数的局部状态,但是yield语句会暂停该函数并且保留其局部状态。实际上,这意味着局部变量和生成器函数的执行状态只是暂时隐藏起来,不会被完全抛弃,再次调用生成器的next()能够恢复执行函数。

 

 

 这使得生成器能够完全兼容迭代器协议。

 

能够停下来的生成器

在基于类的迭代器中,可以通过手动引发StopIteration异常来表示迭代结束。因为生成器与基于类的迭代器完全兼容,所以背后仍然使用这种办法。

1 def repeat_three_times(value):
2     yield value
3     yield value
4     yield value
5 
6 
7 for x in repeat_three_times("hello"):
8     print(x)

 

 

 该生成器在迭代三次后停止产生新值,可以确认这是通过当执行到函数结尾时引发StopIteration异常来实现的。通过下面代码来实验:

 1 def repeat_three_times(value):
 2     yield value
 3     yield value
 4     yield value
 5 
 6 
 7 iterator = repeat_three_times('Hello')
 8 print(next(iterator))
 9 print(next(iterator))
10 print(next(iterator))
11 print(next(iterator))

 

 

 这个迭代器表现和预期一样,一旦到达生成器函数的末尾,就会不断抛出StopIteration以表示所有的值都用完了。

def repeat_three_times(value):
    yield value
    return value
    yield value


iterator = repeat_three_times('Hello')
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))

当在生成器中使用return语句时会终止迭代并且产生StopIteration异常。

 

 

生成器表达式

关键要点

  • 生成器表达式与列表解析式类似,但是不构造列表对象,而实像基于类的迭代器或者生成器函数那样“即时”生成值。
  • 生成器表达式一经使用就不能重新启动或者重新使用。
  • 生成器表达式最适合实现简单的"实时"迭代器,但是对于复杂的迭代器,最好编写生成器函数或者基于类的迭代器。

生成器为编写基于类的迭代器提供了语法糖,生成器表达式在此基础上又添加了一层语法糖。

来看一个例子:

1 iterator = ("hello" for i in range(3))
2 print(iterator)

 

 上面的代码和下面的代码原理相同。

1 def bounded_repeater(value, max_repeats):
2     for i in range(max_repeats):
3         yield value
4         
5         
6 iterator = bounded_repeater("hello", 3)
1 iterator = ("hello" for i in range(3))
2 for x in iterator:
3     print(x)
4     
5 
6 for x in iterator:
7     print(x)

 

 生成器表达式一经使用就不能重新启动。

 

迭代器链

关键要点:

  • 生成器可以链接在一起形成高效而且可以维护的数据处理管道。
  • 互相链接的生成器会逐个处理在链中通过的每个元素。
  • 生成器表达式可以用来编写简洁的管道定义,但是可能会降低代码的可读性。

Python中的迭代器有一个重要特性:可以链接多个迭代器,从而编写高效的数据处理"管道".

 1 def integers():
 2     for i in range(1, 9):
 3         yield i
 4 
 5 
 6 def squard(seq):
 7     for i in seq:
 8         yield i * i
 9 
10 
11 def negated(seq):
12     for i in seq:
13         yield -i
14 
15 
16 chain = negated(squard(integers()))
17 print(list(chain))

 

 如上面代码所示:

  1. integers生成器产生一个值,比如3
  2. 这个值"激活"squared生成器来处理,得到3*3=9,并且将其传递到下一个阶段。
  3. 由squared生成器产生的平方数立即送入negated生成器,将其修改为-9并再次yield.

生成器链可以高效执行并且很容易修改,因为链中的每一步都是一个单独的生成器函数。

下面的代码和上面代码原理一样;

1 integers = range(8)
2 squared = (i * i for i in integers)
3 negated = (-i for i in squared)
4 print(negated)
5 print(list(negated))

 

 使用生成器表达式的唯一缺点式不能再使用函数参数进行配置,也不能在同一处理管道中多次重复使用相同的生成器表达式。

不过在构建这些管道时自由组合和匹配生成器表达式和普通生成器,有助于提高复杂管道的可读性。

posted @ 2020-11-10 09:25  蟹老板bb  阅读(485)  评论(0编辑  收藏  举报