Python迭代器与生成器详解

学习python的迭代器与生成器的时候,需要了解容器(Container)、可迭代对象(iterable)、迭代器(iterator)和生成器(generator),这样就可以知道一些数据类型为什么可以被for循环,自己如何写出可以迭代的方法等。

python

0x00 可迭代对象与迭代器的发现

通过for循环可以很方便的遍历一些字符串、列表、元祖及字典等,而当你遍历一个int类型的时候,IDE会报出一个TypeError: ‘int’ object is not iterable的错误,告诉你这不是一个可迭代对象,因而无法遍历它。

通过dir([])(以列表为例),可以查看列表的“双下方法”。其中__iter__表示其为一个可迭代对象,通过该方法可以生成一个迭代器。

#查看列表的双下方法
dir_list = dir([])
#生成列表的迭代器
g = dir([]).__iter__()
#查看迭代器的方法
iter_list = dir(g)
#查看迭代器与可迭代对象的双下方法交集
print(set(iter_list) - set(dir_list))

'''
{'__length_hint__', '__next__', '__setstate__'}
'''

可以看见迭代器比可迭代对象多了那么几个方法,其中最重要的就是__next__。

  • 只要含有__iter__方法的都是可迭代对象——可迭代协议
  • 内部含有__iter__和__next__方法的就是迭代器——迭代器协议

因而可迭代对象可以变遍历,被for循环。其中迭代器最大的有点,不仅仅是方便遍历,并且可以聪明的处理数据,比较省内存。

0x01 生成器函数与生成器

生成器函数是生成生成器的方法之一。其主要表现在函数中有关键件“yield”。

def generator():
    print(1)
    yield 'a'
    print(2)
    yield 'b'
    print(3)

g = generator()
ret = g.__next__()
print(ret)
ret = g.__next__()
print(ret)
ret = g.__next__()
print(ret)

其执行结果为1    a    2     b     3    StopIteration

其执行顺序(数字为行号)为:1 8 9 2 3 10 4 5 13 6 抱错。每次__next__后就执行到yield,之后就跳出函数,出来执行外部函数,直到执行下一次__next__,又跳入函数,执行到下一个yield,知道最后没有找到yield,因而抱错StopIteration。

生成器函数并不能直接遍历,需要先生成一个生成器(8),之后才可以用for去遍历生成器。

Ps.生成器可以通过list()强制转换为列表,当然没有什么实质性的用,并且还会浪费内存。

0x02 生成器的进阶使用

迭代器的方法__next__,其还有一个类似方法__send__,不过__send__传入一个参数。

def generator():
    print(1)
    content = yield 'a'
    print(content,2)
    yield 'b'
    print(3)
    yield 'c'

g = generator()
ret = g.__next__()
print(ret)
ret = g.send('Hello')
print(ret)
ret = g.__next__()
print(ret)

'''
1
a
Hello 2
b
3
c
'''

当代码执行到第一个g.__next__()的时候,跳入生成器函数执行到yield ‘a’,由于先只想yield ‘a’就跳出了函数,=的复制因而无法完成就跳出函数执行外部函数代码;执行到g.send(‘Hello’)的时候,给上一个yield的位置传入参数’hello’之后赋值给content,再执行代码到yield ‘b’。

其__send__的作用为在获得下一个值的同时,给上一个yield的位置传递一个数据。

0x03 生成器表达式

生成器的产生出了可以通过生成器函数生成外,还可以通过生成器表达式生成。

在学习列表的时候,可以通过列表解析来生成一个列表。如下:

list = [i for i in range(1,101) if i%2 == 0]

这样就可以生成一个100以内的偶数列表,也可以使用近乎同样的方法生成一个生成器表达式,仅需将[]改为()。

generator = (i for i in range(1,101) if i%2 == 0)

这样就生成一个生成器。

0x04 生成器与生成器表达式的运行流程

a.求输入数字的移动平均值。

def wrapper(func):
    def inner():
        g = func()
        g.__next__()
        return g
    return inner

@wrapper
def average():
    sum = 0
    counter = 0
    avg = 0
    while True:
        num = yield avg
        sum += num
        counter += 1
        avg = sum/counter

g = average()

while True:
    num = int(input('>>>'))
    print(g.send(num))

该例子,由于第一次yield返回值为无效值(第一次yield值无法传入send()中的数据,所以第一次为无效返回)。因而先需要__next__()一次,因而将该过程丢入到了装饰器中,并预激生成器的装饰器,这样用户在输入时候就自动执行了一次__next__()忽略了无效值。

b.装饰器的执行流程1

def demo():
    for i in range(4):
        yield i

g=demo()

g1=(i for i in g)
g2=(i for i in g1)

print(list(g1))
print(list(g2))

'''
[0, 1, 2, 3]
[]
'''

在考虑执行顺序的时候,一定要清楚迭代器只能用一次,用完后之后在调用,该生成器就为空了,所以即使10行11行代码互换,执行结果也一样的。生成器表达式与生成器函数一样,不取值的时候,不会执行函数或者表达式中的代码。

c.装饰器的执行流程2

def add(n,i):
    return n+i

def test():
    for i in range(4):
        yield i

g=test()
for n in [1,10]:
    g=(add(n,i) for i in g)

print(list(g))

'''
[20, 21, 22, 23]
'''

主体思路和上一个代码的考虑方式一样,不过这段代码中,循环较为复杂,可以将最后的for循环进行代码优化后再看。

n = 1
g = (add(n,i) for i in g)
n = 10
g = (add(n,i) for i in g)

代码还可以改为:

n = 10
g=(add(n,i) for i in (add(n,i) for i in g))

这样就可以算出最终结果了。

发表评论