Python中可迭代对象、迭代器和生成器相关

Blog地址:https://www.jiangdog.com/blog/iterator-and-generator

可迭代对象 Iterable

  1. 可迭代对象的定义
    • 如果一个对象实现了__iter__方法,且使用iter()内置函数可以获取迭代器对象,那么该对象就是可迭代对象。
    • 除上述之外,序列都是可迭代对象。且若一个对象实现了__getitem__方法,且其参数是从0开始的索引,这种对象也是可迭代对象。
  2. iter() 函数作用
    • 尝试调用该对象的__iter__方法获取一个迭代器。
    • 若没有实现__iter__方法,却实现了__getitem__方法,Python创造一个迭代器,并有序的(从0开始)获取元素。
    • 如果尝试失败,抛出TypeError异常。
  3. 判断是否是可迭代对象

    • 如果该对象实现了__iter__方法,则其一定是可迭代对象

      class Foo:
          def __iter__(self):
              pass
      
      print(issubclass(Foo, abc.Iterable))  # True
      print(isinstance(Foo(), abc.Iterable))  # True
      
    • 任何Python的序列都是可迭代的,因为他们都实现了__getitem__方法,事实上Python中的标准序列也都实现了__iter__方法,对__getitem__做特殊处理是为了向后兼容,自定义序列时最好的做法是两者都实现。

    • 对于只实现了__getitem__方法的可迭代对象,使用isinstance()来判断并不准确,因为iter()函数考虑到了遗留的__getitem__问题,而abc.Iterable并没有。

      class MyList:
          def __init__(self):
              self.seq = [1, 2, 3]
      
          def __getitem__(self, index):
              return self.seq[index]
      
          def __len(self)__:
              return len(self.seq)    
      
      print(isinstance(MyList(), abc.Iterable))  # False
    • 最好的方法是在尝试迭代不可迭代对象时,会抛出TypeError异常,此时可以try/except捕获异常后再做进一步处理。

      class MyObject:
          def __init__(self):
              pass
      
      try:
          iter(MyObject())
      except TypeError as e:
          print(e)  # 'MyObject' object is not iterable
  4. iter() 函数的另一种用法

    • 内置iter() 函数

      def iter(source, sentinel=None): # known special case of iter
        """
      
        iter(iterable) -> iterator
        iter(callable, sentinel) -> iterator
        Get an iterator from an object.  In the first form, the argument must
        supply its own iterator, or be a sequence.
        In the second form, the callable is called until it returns the sentinel.
      
        """
        pass

      第二种用法种,接收一个可调用对象和终止哨符,此时返回一个迭代器,该迭代器会不停的产出传入的可调用对象返回的值,直到出现终止哨符,则耗尽,抛出StopIteration异常,且不产出终止哨符。

      def random_for_one():
       return randint(1, 6)
      
      for_one_iterator = iter(random_for_one, 1)
      print(for_one_iterator)  # 
      
      for v in for_one_iterator:
        print(v)  # 3 4 5 4 5

迭代器 Iterator

  1. 迭代器含义和特点

    • 标准的迭代器,需要实现__iter____next__两个方法。
    • __iter__返回迭代器本身,以便应该在使用可迭代对象的地方使用迭代器,如for循环。
    • __next__返回迭代器中的下一个有效元素,直到没有元素抛出StopIteration异常。
    • 利用isinstance(x, abc.Iterator)来判断对象x是否是迭代器。
    • 迭代器迭代到头耗尽,无法还原,需要重新构建迭代器。
    • 迭代器

    迭代器是这样的对象:实现了无参数的 __next__方法,返回序列
    中的下一个元素;如果没有元素了,那么抛出 StopIteration 异常。
    Python 中的迭代器还实现了 __iter__ 方法,因此迭代器也可以迭代。

  2. 迭代器和可迭代对象之间的关系

    • Python从可迭代对象获取迭代器。
    • 迭代器也是可迭代的,因为其实现了__iter__方法,且返回迭代器自身。
    • 可迭代对象一定不能实现__next__方法。
    • 为了支持多种遍历,如for,拆包等,必须一个可迭代实例中获取多个独立迭代器,且迭代器能够维护自身的状态,正确的做法是对可迭代对象上调用__iter()方法,每次都构建一个独立的新的迭代器;而不是在可迭代对象中实现__next__方法,使其是自身的迭代器,若这样做,其在一次迭代耗尽后将无法构建新的迭代器。

       # no __next__ in Iterable
       class ErrorSentence:
           def __init__(self, text):
               self.text = text
               self.words = RE_WORD.findall(text)
               self.index = 0
      
           def __iter__(self):
               return self
      
           def __next__(self):
               try:
                   word = self.words[self.index]
               except IndexError:
                   raise StopIteration
               self.index += 1
               return word
      
      error_sentence = ErrorSentence('x y z')
      error_iterator = iter(error_sentence)
      print(error_iterator)  # <__main__.ErrorSentence object at 0x000001C38F24CE10> 
      print(isinstance(error_sentence, abc.Iterator))  # True
      
      for v in error_sentence:
          print(v)  # x y z
      
      
      # 可迭代对象是自身的迭代器
      
      
      # 迭代器已耗尽,不能再次迭代
      
      for v in error_sentence:
          print(v)
  3. for循环的实现
    • 判断对象是否是可迭代对象,如果不是则直接抛出TypeError
    • 如果该对象是可迭代对象,则通过iter(iterable)方法,调用其__iter__方法返回一个迭代器;若该对象未实现__iter__接口,但存在__getitem__方法,Python会创造一个迭代器。
    • 不断调用next(iterator),执行迭代器的__next__方法,使其按序的返回下一个可用的值。
    • 如果迭代到头,没有元素了,迭代器耗尽抛出StopIteration异常,Python语言内部将会处理这个异常(除了for之外,其他迭代上下文,如列表推导式、元组拆包等也会处理该异常)。
  4. 定义一个简单的迭代器

    • 由上述可知一个标准的迭代器需要实现__iter____next__方法,展示相关示例。

      RE_WORD = re.compile('\w+')
      
      
      class Sentence:
          def __init__(self, text):
              self.text = text
              self.words = RE_WORD.find('text')
      
          def __iter__(self):
              return SentenceIterator(self.words) 
      
      class SentenceIterator:
          def __init__(self, words):
              self.words = words
              self.index = 0
      
          def __iter__(self):
              return self
      
          def __next__(self):
              try:
                  word = self.words[self.index]
              except IndexError:
                  raise StopIteration
      
              self.index += 1
              return word
      
      abc_sentence = Sentence('a b c')
      sentence_iterator = iter(abc_sentence)
      print(sentence_iterator)  # <__main__.SentenceIterator object at 0x0000025F31C5B9B0>
      print(isinstance(sentence_iterator, abc.Iterator))  # True
      
      for v in abc_sentence:
          print(v)  # a b c
    • 使用生成器函数代替SentenceIterator迭代器类:

       RE_WORD = re.compile('\w+')
      
      
       class Sentence:
           def __init__(self, text):
               self.text = text
               self.words = RE_WORD.findall(text)
      
           def __iter__(self):
               for word in self.words:
                   yield word
      
       sentence_iterator = iter(Sentence('a b c'))  
       print(sentence_iterator)  # 

      此时这里调用iter()所获得的迭代器实际上是一个实现了迭代器接口的生成器对象。


生成器 Generator

  1. 生成器含义和特点
    • 只要Python函数的定义体中出现了yield关键字,则该函数就是生成器函数。调用该生成器函数返回一个生成器对象。
    • 把生成器传给next()函数时,生成器函数会向前,执行生成器函数中下一个yield语句返回产出值并在当前位置暂停。
    • 当生成器函数返回时,生成器对象抛出StopIteration异常。
  2. 生成器函数和生成器表达式

    • 普通函数和生成器函数唯一的区别就是,生成器函数在定义体中存在yield关键字。

       # 生成器函数
       def gen_123():
           yield 1
           yield 2
           yield 3
      
       print(gen_123)  #  函数对象
       print(gen_123())  #   生成器对象
      
      for v in gen_123():
          print(v)  # 1 2 3
      
      g = gen_123()
      
      while True:
          # 调用next(g)来不断获取生成器的下一个值
          try:
              print(next(g))
          except StopIteration:
              # 生成器函数的定义体执行完后抛出异常后终止
              break
    • 生成器表达式

      生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。也就是说,如果列表推导是制造列表的工厂,那么生成器表达式就是制造生成器的工厂。

      
      # 生成器表达式
      
      gen_ABC = (s for s in 'ABC')
      print(gen_ABC)  #  at 0x000001F4038E9410>

      此时通过该生成器表达式构建了一个生成器对象。

  3. 标准库中的生成器函数

    • itertoolsfunctools以及内置方法中有很多生成器函数。
    • 最常见的是filtermapenumerateallanyreversed等等。
    • itertools中提供了很多过滤相关、映射相关(itertools.accumulate(it, [func]))、合并多个迭代对象相关(itertools.product()求笛卡儿积、itertools.zip_longest())、输入的各个元素扩展成多个输出元素的生成器函数(itertools.combinations(it, out_len)组合, itertools.combinations_with_replacement(it,
      out_len)
      包含相同元素的组合)。
  4. 生成器对象的其他方法和功能

    • generator.__next__()只允许客户从生成器中获取数据,generator.send(value)允许客户代码和生成器之间双向交换数据。

       # generator.send(value)
       def gen():
           print('start')
           value = 0
           while True:
               receive = yield value
               print(receive)
               if receive == 'end':
                   break
               value += 1
      
       g = gen()
       # 或next(g) 预激生成器
       print(g.send(None))  # 0
       print(g.send('a'))  # 1
       print(g.send('b'))  # 2
       try:
           print(g.send('end'))
       except StopIteration:
           print('end')
    • generator.close()能够关闭生成器,关闭生成器后继续调用next()方法后将会抛出异常。

       # generator.close()
       def gen():
           yield 1
           yield 2
           yield 3
      
       g = gen()
       print(next(g))
       print(next(g))
      
       g.close()
       try:
           print(next(g))
       except StopIteration:
           pass

两者的比较

  1. 接口:

    • Python迭代器协议定义了两个方法:__iter____next__
    • 生成器对象实现了这两个方法,因此,所有生成器都是迭代器。

       g = (i for i in 'abc')
       print(g.__iter__)  # <method-wrapper '__iter__' of generator object at 0x02ACAF00>
       print(g.__next__)  # <method-wrapper '__next__' of generator object at 0x02ACAF00>
      
  2. 实现方式:
    • 生成器通过含有yield关键字的函数或生成器表达式来编写,这两种方式返回的生成器对象属于types.GeneratorType类型,且types.GeneratorType类型的实例实现了迭代器接口,则所有的生成器都是迭代器。
    • 但我们可以编写不是生成器的迭代器对象,如简单实现__iter____next__方法,或者用C语言编写拓展。
    • The type of generator-iterator objects, created by generator functions.
  3. 其他:
    • Iterator Types
    • Generator Types

参考

  1. 大部分总结参考自《Fluent Python》第五部分第14章节

  2. 生成器(generator)概念


其他

  1. 文中代码

你可能感兴趣的:(python)