美文网首页
Python中的迭代器与生成器

Python中的迭代器与生成器

作者: SOLAREST | 来源:发表于2016-12-27 20:11 被阅读66次

迭代器(Iterable)

  • 简单来说,迭代器对象(my_list)可以让以下代码正常工作:
for i in my_list:
    ...

for i in iter(my_list):
    ...

for i in (v for v in my_list if v is not None):
    ...
  • 如果对象实现了__iter__()就可以使用迭代器。我们可以手动实现迭代协议(iterator protocol)来实现对对象的迭代操作。值得注意的是,这里的对象特指Iterator
class Counter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def __next__(self):  # Python 2: def next(self)
        if self.current > self.high:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1

if __name__ == '__main__':
    c = Counter(1, 5)
    for i in c:
        print(i)

在看这段代码之前,让我们先来明确一个概念:

就Python中的迭代器而言有两个含义:第一个是Iterable,第二个是Iterator
协议规定Iterable的__iter__()方法会返回一个Iterator,Iterator的__next__()方法(Python 2里是next())会返回下一个Iterator对象,如果迭代结束则抛出StopIteration异常。
同时,Iterator自己也是一种Iterable,所以也需要实现Iterable的接口,也就是__iter__(),而Iterator的__iter__()只需要返回自己就行了。

明确之后,我们来看看上述代码发生了什么。首先for语句判断对象c是一个Iterable(因为实现了__iter__())于是调用它的__iter__()方法返回了一个Iterator对象,接着for语句继续会调用它的__next__()方法(因为此时起作用的是Iterator对象)返回下一个Iterator,以此往复直到抛出StopIteration异常。

  • 但是python的for方法对未实现迭代协议的对象也进行了兼容,比如我们熟悉的:
    for key in {"a": 1, "b": 2}:
      ...
    

这是因为对于实现了__getitem__的对象for方法会改用以下标迭代的方式:

class Counter:
    def __init__(self, low, high):
        self.low = low
        self.high = high

    def __len__(self):
        return self.high - self.low

    def __getitem__(self, key):
        index = self.low + key
        if self.low <= index <= self.low + len(self):
            return index
        else:
            raise IndexError

if __name__ == '__main__':
    c = Counter(1, 5)
    for i in c:
        print(i)

这一点与Iterator本身无关,但是却属于Iterable的范畴。

我们熟悉的Python的内建对象dictlist都是可迭代的(Iterable),但是它们满足的是序列协议(sequence protocol)故可迭代。

实际上:

iterable: 实现了__iter__()__getitem__()方法的对象。
iterator: 实现了 iterator protocol(即方法:__next__()__iter__())的iterable对象。
sequence:实现了 sequence protocol(即方法: __getitem__()__len__()),并能使用整数索引访问元素的iterable对象。

这一点可以参考 PEP 234
同时,附一张图说明它们之间关系:

Iterables

生成器(Generators)

  • 首先Generators也是一个Iterator对象,内部也实现了__iter__()__next__()方法,我们可以使用以下方法使用生成器:

    1. 在函数中使用yield
    def count_generator():
        i = 0
        while True:
            i += 1
            yield i
    
    if __name__ == '__main__':
        a = count_generator()
        print(a)               # <generator object count_generator at 0x7f2474adda40>
        print(next(a))      # 1
        print(next(a))      # 2
        print(next(a))      # 3
    
    1. 生成器表达式
    a = (i for i in range(1, 10))
    
    print(a)                    # <generator object <genexpr> at 0x01291720>
    print(next(a))           # 1
    print(next(a))           # 2
    print(next(a))           # 3
    
  • Iterator有所区别的是,Generators迭代的本质就是通过next()__next__()方法来调用send()方法(可以参考PEP 342)。我们可以用以下方式实现类似Iterable的迭代:

  c = (i for i in range(1, 10))
  for i in range(1, 10):
    print(c.send(None))
  • 所以既然我们可以使用send()方法来控制生成器的输出,当然我们也可以实现从外部对生成器输出的控制,来看一看下面这段没什么用的代码:
def generater_list(i=1):
    while i < 10:
      value = (yield i)
      if value:
        i += value
      else:
        i += 1

if __name__ == '__main__':
    g = generater_list()
    print(next(g))  # 1
    print(next(g))  # 2
    g.send(4)
    print(next(g))  # 7
    print(next(g))  # 8

在上述代码中,send()方法,通过yield给value赋值并将函数挂起。当使用next()时,生成器函数的返回值就相应的发生了改变。

如果需要直接调用 send(),第一次请务必 send(None) 只有这样,一个 Generator 才算是真正被激活了。我们才能进行下一步操作。

  • 为什么要使用生成器:

    1. 对内存友好

    也许当处理处理上GB的文件时,将它全部读入内存不会再是一个明智的选择,我们可以把它放到生成器函数中:

    def file_reader(file_path):
          with open(file_path, "rt") as f:
              for line in f:
                  yield line
    
    1. 实现协程

    生成器的特性十分适合完成一些调度作业,比如说下面这个例子:

      from collections import deque
      
      def count_down(n):
          while n > 0:
              print('T-minus', n)
              yield
              n -= 1
          print('Blastoff!')
    
      def count_up(n):
          x = 0
          while x < n:
              print('Counting up', x)
              yield
              x += 1
    
      class TaskScheduler:
          def __init__(self):
              self._task_queue = deque()
    
          def new_task(self, task):
              self._task_queue.append(task)
    
          def run(self):
              while self._task_queue:
                  task = self._task_queue.popleft()
                  try:
                      # Run until the next yield statement
                      next(task)
                      self._task_queue.append(task)
                  except StopIteration:
                      # Generator is no longer executing
                      pass
    
      if __name__ == '__main__':
          sched = TaskScheduler()
          sched.new_task(count_down(10))
          sched.new_task(count_down(5))
          sched.new_task(count_up(15))
          sched.run()
    

我们运行一下这段代码,可以发现TaskScheduler类在一个循环中运行生成器集合——每个都运行到碰到yield语句为止,然后马上运行我们定义好的两个函数中的另一个。

Reference

  1. http://manjusaka.itscoder.com/2016/09/11/something-about-yield-in-python/
  2. http://blog.chinaunix.net/uid-15174104-id-4172583.html
  3. http://www.oschina.net/translate/improve-your-python-yield-and-generators-explained
  4. https://www.zhihu.com/question/44015086
  5. 《Python Cookbook》

相关文章

网友评论

      本文标题:Python中的迭代器与生成器

      本文链接:https://www.haomeiwen.com/subject/atuevttx.html