Python迭代器和生成器

目录

      • 1.迭代器协议
      • 2.创建序列
      • 3.创建生成器
      • 4递归式生成器
      • 5.通用生成器
      • 6.生成器的方法

1.迭代器协议

迭代意味着重复多次,就像循环一样。
for循环可以迭代序列,字典,实现了方法_iter_的对象。
方法_iter_返回一个迭代器,它是包含方法_next_的对象,而调用这个方法可以不提供任何参数。当你调用方法_next_时,迭代器应返回下一个值。如果迭代器没有可供返回的值,应引发StopIteration异常。也可以使用内置函数next,next(it)和it .next()等效。
示例:斐波那契数列

class Fibs:
    def __init__(self):
        self.x=0
        self.y=0
    def __next__(self):
        self.x,self.y = self.y,self.x + self.y
        return self.x
    def __iter__(self):
        return self
fibs=Fibs()
for f in fibs:
    if f>100:
        print(f)
        break

迭代器实现了方法__iter__,这个方法返回迭代器本身,大多数情况下,都在另一个对象中实现返回迭代器的方法__iter__,并在for循环中使用这个对象。
推荐在迭代器中也实现方法__iter__(并像刚才那样使它返回self),这样迭代器就可以直接用于for循环。
⚠️⚠️⚠️
实现了方法__iter__的对象是可迭代的
实现了方法__next__的对象是迭代器

2.创建序列

除了对迭代器和可迭代对象进行迭代之外,还可将它们转化为序列。
在可以使用序列的情况下,大多数也可使用迭代器或可迭代对象,可以使用构造函数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, 2, 3, 4, 5, 6, 7, 8, 9, 10]

3.创建生成器

生成器创建起来像函数一样简单。
创建一个将嵌套列表展开的函数,
以这个列表作为参数:nested=[[1,2],[3,4],[5]]

def flatten(nested):
    for sublist in nested:
        for element in sublist:
            yield element
nested=[[1,2],[3,4],[5]]
for num in flatten(nested):
    print(num)#或替换为
    print(list(flatten(nested)))
#运行结果
1
2
3
4
5
#或
[1, 2, 3, 4, 5]

这个代码首先迭代嵌套列表中的所有子列表,然后按顺序迭代每个子列表的元素。
在这里没有yield语句。包含yield语句的函数被称为生成器
生成器和普通函数的区别在于,生成器不是使用return返回一个值,而是可以生成多个值,每次一个。每次使用yield生成一个值后,函数都将停止执行,等待被重新唤醒。重新换新后将从停止的地方开始继续执行。

4递归式生成器

上述的生成器只能处理两层的嵌套列表,是通过两个for循环来实现的。那要处理任意层嵌套的列表该如何办呢?
这就该求助于递归了。

def flatten(nested):
    try:
        for sublist in nested:
            for element in flatten(sublist):
                yield element
    except TypeError:
        yield nested
print(list(flatten([[1],[2,3],4,[5,[6]],7,[8,[9,[10]]]])))
#运算结果
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

调用flatten时,有两种可能性(处理递归时都如此):基线条件和递归条件。在基线条件下,要求函数展开单个元素(如一个数)。这时,for循环将引发TypeError异常,而这个生成器只生成一个元素。然而,如果要展开一个序列或其他的可迭代对象,就需要遍历所有的子列表(其中有些可能不是列表)并对它们调用flatten,然后使用另一个for循环生成展开后的子列表中的所用元素。
但是,这种解决方案有一个问题。如果nested是字符串或类似于字符串的对象,它就属于序列,因此不会引发TypeError异常。但是你并不想对其进行迭代。


⚠️⚠️⚠️
在函数flatten中,不应该对类似于字符串的对象进行迭代,主要原因有两个。首先,你想将类似于字符串的对象视为原子值,而不是应该展开的序列。其次,对这样的对象进行迭代会导致无穷递归,因为字符串的第一个元素是一个长度为1的字符串,而长度为1的字符串的第一个元素是字符串本身!


要处理这种问题,必须在生成器开头进行检查。要检查对象是否类似于字符串,最好的方式是,尝试将对象与一个字符串拼接起来,并检查这是否会引发TypeError异常°。添加这种检查后的生成器如下:

def flatten(nested):
    try:
        #不迭代类似于字符串的对象:
        try:nested +''
        except TypeError:pass
        else:raise TypeError
        for sublist in nested:
            for element in flatten(sublist):
                yield element
    except TypeError:
        yield nested
print(list(flatten(['foo',['age',['bar'],'baz']])))

如你所见,若表达式nested +''引发了TypeError异常,就忽略这种异常;若没有引发TypeError异常,内部try语句中的else子句将引发TypeError异常,这样在外部的except子句中原封不动的生成类似于字符串的对象。

#运行结果
['foo', 'age', 'bar', 'baz']

这里没有执行类型检查:没有检查nested是否字符串,而只是检查其行为是否类似于字符串,即能否与字符串拼接。对于这种检查,可以使用isinstance以及字符串和类似于字符串的对象的一些抽象超类来替换,但是没有这样的标准类。另外,即使是对UserString来说,也无法检查其类型是否为str。

5.通用生成器

生成器是包含关键字yield的函数,在被调用时不会执行函数体内的代码,而是返回一个迭代器。每次请求值时,都将执行生成器的代码,直到遇到yield或return。yield意味着生成一个值,而return意味着生成器应停止运行(即不再生成值;仅当生成器调用return时,才能不提供任何参数)。
生成器由两个单独的部分组成:生成器的函数生成器的迭代器。生成器的函数由def语句定义,其中包含yield。生成器的迭代器是这个函数的返回结果。两者通常被视为一个,统称为生成器。

def simple_generator():
    yield 1
print(simple_generator)
print(simple_generator())
#运行结果
<function simple_generator at 0x1082bfeb0>
<generator object simple_generator at 0x1083a4190>

对于生成器返回的迭代器,可以像使用其他迭代器一样使用它。

6.生成器的方法

当生成器开始运行后,可使用生成器和外部的通信渠道向它提供值。这个渠道包含两个端点:
外部世界:可访问生成器的方法send,这个方法类似于next,但接受一个参数(可以是任何对象)。
生成器:在生成器内部,yield可能用作表达式而不是语句。换句话说:在挂起的生成器内部,yield返回一个值——通过send从外部发送的值。如果使用的是next,yield将返回None。
注意:仅当生成器被挂起(即遇到第一个yield)后,使用send才有意义。若要在此之前想生成器提供信息,可使用生成器的函数的参数。


⚠️⚠️⚠️
如果一定要在生成器刚启动时对其调用方法send,可向它传递参数None


def repeater(value):
    while True:
        new=(yield value)
        if new is not None:value=new
r=repeater(10)
print(next(r))
print(r.send('你好'))
#运行结果
10
你好

注意到使用圆括号将yield表达式括起来了。在有些情况下,并非必须要这样做,但小心总没错。如果要以某种方式使用返回值,将其用圆括号括起吧。
方法throw: 用于在生成器中(yield表达式处)引发异常,调用时可提供一个异常类型、一个可选值和一个traceback对象。
方法close: 用于停止生成器,调用时无需提供任何参数。
方法close(由Python垃圾收集器在需要时调用)也是基于异常的:在yield处引发GeneratorExit异常。所以要在生成器中提供一些清理代码,可将yield方法放在一条try/finally语句中。也可以选择捕获GeneratorExit异常,但随后必须重新引发它(可能在清理后)、依法其他异常或直接返回。对生成器调用close后,再试图从它那里获取值将导致RuntimeError异常。

你可能感兴趣的:(python,python,开发语言)