Python3中的迭代器和生成器

介绍

本篇将介绍Python3中的迭代器与生成器,描述可迭代与迭代器关系,并实现自定义类的迭代器模式。python

可迭代的(iterable)

Python标准库中存在着一些可迭代对象,例如:list, tuple, dict, set, str等。
能够对这些迭代对象,进行for-in等迭代操做,例如:express

for s in "helloworld":
    print(s)

编译器若想迭代一个对象a,则会自动调用iter(a)获取该对象的迭代器(iterator),若是iter(a)抛出异常,则对象a不可迭代。函数

判断对象是否可迭代

原生函数iter(instance) 能够判断某个对象是否可迭代,它的工做流程大概分为如下3个步骤:spa

  1. 检查对象instance是否实现了__iter__方法,并调用它获取返回的迭代器(iterator)。
  2. 若是对象没有实现__iter__方法,可是实现了__getitem__方法,Python会生成一个迭代器。
  3. 若是上述都失败,则编译器则抛出TypeError错误,‘xxx' Object is not iterable。

自定义类实现__iter__方法

根据第一条,咱们自定义类Iter1实现__iter__方法使该类的对象可迭代。code

class Iter1:
    def __init__(self, text):
        self.text = text

    def __iter__(self):
        return iter(self.text)

iter1 = Iter1("hello")
for s in iter1:
    print(s)

Iter1类实现了__iter__方法,经过iter()调用,获得可迭代对象text的迭代器并返回,实现了迭代器协议,所以能够经过for-in等方式对该对象进行迭代。
第二条一般都是针对Python中的序列(sequence)而定义,例如list,为了实现sequence协议,须要实现__getitem__方法。对象

class Iter2:
    def __init__(self, sequence):
        self.sequence = sequence

    def __getitem__(self, item):
        return self.sequence[item]


iter2 = Iter2([1, 2, 3, 4])
for s in iter2:
    print(s)

实际上,为了不版本后序改动,Python标准库中的序列除了实现了__getitem__方法,也实现了__iter__方法,所以咱们在定义序列时也应实现__iter__。
综上,若是显示判断某个对象是否可迭代,应该调用iter(instance)是否抛出异常,由于只实现了__getitem__的序列也是可迭代的(例子中Iter2的对象是可迭代的,但isinstance(iter2, abc.Iterator)返回结果是False)。同时,若是在调用iter后进行迭代操做没必要显示判断,能够用try/except方式包装代码块。blog

iterable vs iterator(可迭代vs迭代器)

iterable定义ip

任何能够由原生函数iter获取到迭代器的对象
任何实现了__iter__方法并返回迭代器的对象
全部的序列(实现了__getitem__)

Python经过获取到可迭代对象的迭代器(iterator)实现迭代,例如for-in的实现实际上是在内部获取到了迭代器进行操做。for-in机制能够理解为下述代码:get

s = 'hello'
it = iter(s)
while (True):
    try:
        print(next(it))
    except StopIteration:
        del it
        break

StopIteration异常将在迭代器耗尽后被抛出,for-in、生成式(comprehension)、元组解压(tuple unpacking)等迭代操做都会处理并这个异常。generator

迭代器是个迭代值生产工厂,它保存迭代状态,并经过next()函数产生下一个迭代值。实现迭代器须要实现如下两个方法:

  1. __iter__
    返回self
  2. __next__
    返回下一个可用的元素,若是无可用元素则抛出StopIteration异常

迭代器实现__iter__,所以全部的迭代器都是可迭代的,下图展现了iterable和iterator的结构。

clipboard.png

迭代器模式

实现一个自定义的迭代器模式须要两个类,分别为实现了__iter__方法的类和经过__iter__返回的迭代器实例类(实现了__iter__和__next__方法)。下面例子简单实现了上述功能。

class IterText:
    def __init__(self, text):
        self.text = text

    def __iter__(self):
        return IteratorText(self.text)


class IteratorText:
    def __init__(self, text):
        self.text = text
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        try:
            letter = self.text[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return letter

text = IterText("hey")
for l in text:
    print(l)

可迭代的IterText实现了__iter__方法,返回了迭代器IteratorText实例。IteratorText实现了__next__方法返回下一个迭代元素直到抛出异常,同时IteratorText实现了__iter__方法返回自身对象用于迭代。
这里的IterText和IteratorText很容易混淆,若是在IterText中实现了__next__方法并将__iter__中返回自身实例self也能够实现上述功能,但一般可迭代对象和迭代器应当分开,这样在可迭代对象中的__iter__中能够返回不一样的迭代器对象,使功能独立。

生成器(generator)

经过上述文章说明,迭代器经过next()不断产出下一个元素直到迭代器耗尽,而Python中的生成器能够理解为一个更优雅的迭代器(不须要实现__iter__和__next__方法),实现了迭代器协议,它也能够经过next()产出元素。
Python中的生成器主要分为两种类型:

  • 生成器函数(generator function)返回获得的生成器:
    包含yield关键字的函数称为生成器函数
def gen_func():
    yield 1
    yield 2
    yield 3
g = gen_func()
  • 生成器表达式(generator expression)返回获得的生成器
g = (i for i in (1, 2, 3))

咱们能够利用生成器进行迭代操做:

for e in g:
    print(e)
    
## 生成器g已被耗尽,若是须要从新迭代须要从新得到新的生成器对象
g = gen_func()
for e in g:
    print(e)

利用生成器代替可迭代中的__iter__迭代器

在迭代器模式章节中,咱们在可迭代IterText中的__iter__返回迭代器IteratorText实例,然而使用生成器的方式会使代码更加优雅。

class IterText:
    def __init__(self, text):
        self.text = text

    def __iter__(self):
        for letter in self.text:
            yield letter

由于yield存在于__iter__,所以__iter__变成了生成器函数,调用它测返回一个生成器,同时生成器又实现了迭代器协议,所以IterText知足了可迭代的需求。

yield from

Python3.3新增长了yield from关键字,解决了yield的嵌套循环。例如itertools中的chain函数,它的简单实现为下面代码。

def chain(*iterable):
    for it in iterable:
        for i in it:
            yield i

print(list(chain('abc', range(3))))
## output: ['a', 'b', 'c', 0, 1, 2]

而使用yield from会使代码变的更加简洁。

def chain(*iterable):
    for i in iterable:
        yield from i

print(list(chain('abc', range(3))))
## output: ['a', 'b', 'c', 0, 1, 2]

# 总结
相关文章
相关标签/搜索