[Python] 一篇文章弄懂迭代器、生成器和yield

1. 迭代器和生成器

  • 迭代器: Python从可迭代的对象中获取迭代器。如果对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的。
  • 生成器: 只要Python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。

区别在于,所有生成器都是迭代器,因为生成器完全实现了迭代器接口。但迭代器不是生成器。

1.1 range迭代器和生成器

# 这是一个iterator
>>> mylist = [x * x for x in range(3)]
>>> for ele in mylist:
...     print(ele, end=" ")
0 1 4

# 这是一个generator
>>> mylist = (x * x for x in range(3))
>>> for ele in mylist:
...     print(ele, end=" ")
0 1 4

1.2 迭代器

迭代器协议:

  • 实现__iter__()方法,返回一个迭代器
  • 实现__next__()方法,返回当前元素,并指向下一个元素的位置,如果当前位置已无元素,则抛出StopIteration异常。

下面我们使用迭代器协议实现一个斐波那契数列:

# 斐波那契数列
class Fib():
    def __init__(self):
        self.a = 1
        self._b = 1

    def __iter__(self):  # 不实现__iter__()报错:'Fib' object is not iterable   Fib不是可迭代的
        return self

    def __next__(self):  # 不实现__next()__报错:iter() returned non-iterator of type 'Fib'  Fib返回了一个不可迭代对象
        if self.a > 100:
            raise StopIteration("终止")

        self.a, self._b = self._b, self.a + self._b
        return self.a


# 1. 直接使用for...in...遍历
f = Fib()
for i, a in enumerate(f):
    try:
        print('return:', a, '\ta: ', f.a, '\tb:', f._b, '\t__next__():', f.__next__())
    except StopIteration:
        print("==StopIteration==")

# 2. 创建迭代器对象遍历可迭代对象
it = iter(Fib())
for i in it:
    print(i, end=" ")
print()

# 3. 使用next()遍历,最后抛出StopIteration
it = iter(Fib())
try:
    while True:
        print(next(it), end=" ")
except StopIteration:
    print("StopIteration")

# dis(Fib)  # 查看Python字节码(类似汇编指令的中间语言)

输出结果:

return: 1 	a:  1 	b: 2 	__next__(): 2
return: 3 	a:  3 	b: 5 	__next__(): 5
return: 8 	a:  8 	b: 13 	__next__(): 13
return: 21 	a:  21 	b: 34 	__next__(): 34
return: 55 	a:  55 	b: 89 	__next__(): 89
==StopIteration==
1 2 3 5 8 13 21 34 55 89 144 
1 2 3 5 8 13 21 34 55 89 144 StopIteration

1.3 生成器函数和生成器

只要Python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。

下面讲一个例子,demo1()里面使用了yield,是一个生成器函数,返回一个生成器。我们可以使用next()方法来迭代生成器,每次迭代next()获取到的就是yield产生的值,也就是yield右边的值

# yield可以理解为暂停按钮,每次执行到yield,保存断点,同时yield还会返回值给调用方
def demo1(value=None):
    print('start')
    yield 1
    yield value
    yield 2
    print('end')

g = demo1("Value")	# 生成器函数也是函数,可以接收传参
print(type(g))   # g是一个生成器
print(next(g))   # 执行yield 1,暂停
print(next(g))   # 执行yield value,暂停
print(next(g))   # 执行yield 2,暂停
print(next(g))   # 找不到yield了,raise StopIteration
<class 'generator'>
start
1
Value
2
end
Traceback (most recent call last):
  File "D:/Desktop/Projects/效率编程/协程/test.py", line 14, in <module>
    print(next(g))   # 找不到yield了,raise StopIteration
StopIteration

总共三次yield,我们是调用方,使用next(g)收到了三个yield返回值,当我们尝试调用第四次时,demo1函数产生了一个StopIteration告诉我们找不到yield了,raise StopIteration。

看一个使用生成器实现斐波那契数列的例子:

def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a  # TODO:每次执行到yield就暂停
        a, b = b, a + b
        counter += 1
        
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
 
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        print("StopIteration")
        break

# print(dis(fibonacci))   # 查看Python字节码(类似汇编指令的中间语言)
print(type(f))  # <class 'generator'> 即类型为生成器对象
print(dir(f))  # 这个对象实现了__iter__()和__next__()

输出结果:

0 1 1 2 3 5 8 13 21 34 55 StopIteration
<class 'generator'>
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']

1.4 遍历生成器的三种方式

可以看到生成器也实现了__iter__()和__next__(),所以可以使用和迭代器一样的遍历方法。

  1. for … in … 遍历
  2. iter() 遍历
  3. list() 转为list
>>> f = fibonacci(10)
>>> for i in f:
...     print(i, end=" ")
...
0 1 1 2 3 5 8 13 21 34 55 >>>
>>> f = fibonacci(10)
>>> for i in iter(f):
...     print(i, end=" ")
...
0 1 1 2 3 5 8 13 21 34 55
>>> f = fibonacci(10)
>>> list(f)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

1.5 使用生成器实现惰性求值

生成器的迭代体验实际上同迭代器完全相同,但其与迭代器的区别在于生成器中的元素只能被循环一次。这是因为生成器不会在内存中存储全部的元素,而是动态地产生下一个将被循环的元素。

使用生成器我们可以实现惰性求值,按行读取大文件之类时,不需要一次将大文件加载到内存,而是需要时,再取出文件的行:[Python] 生成器按行读取大文件

另外,Python内置了很多优秀的生成器,感兴趣可以参考文档: itertools。或者参考《Python Cookbook》,里面介绍了itertools常用的函数。

2. 生成器的next()、send()、throw()、close()

直接上代码!

def echo(value=None):
    print("当next()被调用时,第一次执行开始")
    try:
        while True:
            try:
                value = (yield value) # 第一次next()yield value之后暂停,赋值不会执行。第二次next():赋值yield给value,yield默认为None
            except Exception as e:
                value = e
    finally:
        print("当close()被调用时,别忘了清理")

# 每一个生成器函数被调用之后,它的函数体并不执行
generator = echo(1)

# 当第一次调用next()时,函数开始执行,执行到yield表达式为止
# value = (yield value)只是执行了yield value这个表达式
print(next(generator))          # 1

# 当第二次调用next()时,yield表达式的值复制给了value
# 而yield表达式默认“返回值”就是"None",所以此时value的值就是None
print(next(generator))          # None

# yield有一个返回值,send(value)的作用就是控制yield的返回值value
# next(generator)相当于是generator.send(None)
print(generator.send(2))        # 2
print(generator.send(None))     # None

# yield也可以抛出异常
print(generator.throw(TypeError, "Error")) # Error

# 调用close()yield会抛出GeneratorExit异常并且自行处理
generator.close()

# 调用close()之后,对象不再可用,next()或send()会抛出StopIteration异常
next(generator)
# generator.send(None)

输出结果:

当next()被调用时,第一次执行开始
1
None
2
None
Error
当close()被调用时,别忘了清理
Traceback (most recent call last):
  File "E:/Documents/PythonCode/yield.py", line 35, in <module>
    next(generator)
StopIteration

3 为什么说send()相当于next(),我们看看底层代码!

Python底层是由C语言实现的,让我们摘出next()和send()来观察看看!

static PyObject *
gen_iternext(PyGenObject *gen)
{
    return gen_send_ex(gen, NULL, 0);  # 传入NULL
}


static PyObject *
gen_send(PyGenObject *gen, PyObject *arg)
{
    return gen_send_ex(gen, arg, 0);  # 传入可变参数
}

从上面的代码中可以看到,send和next都是调用的同一函数gen_send_ex,区别在于是否带有参数。

gen_send_ex()参考:https://www.cnblogs.com/coder2012/p/4990834.html

4. 参考博客

Python中的yield关键字:https://www.jianshu.com/p/fb67382a0455
Python yield与实现:https://www.cnblogs.com/coder2012/p/4990834.html
菜鸟教程迭代器和生成器:https://www.runoob.com/python3/python3-iterator-generator.html

5. [Python] 理解yield关键字、生成器函数和协程

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值