当前位置 : 主页 > 编程语言 > python >

Python面试题之Python生成器

来源:互联网 收集:自由互联 发布时间:2022-06-15
首先说明一下生成器也是迭代器,也有迭代器的那些优点。 那为什么要生成器呢?因为到目前为止都 不是你写的迭代器,都是别人定义好的。那如何自己去造一个迭代器呢?下面的内


首先说明一下生成器也是迭代器,也有迭代器的那些优点。

那为什么要生成器呢?因为到目前为止都 不是你写的迭代器,都是别人定义好的。那如何自己去造一个迭代器呢?下面的内容就会给你答案。

想要自己造一个迭代器,我们可以根据迭代器的特征(只要一个对象有​​__iter__​​​和​​__next__​​​方法那它就是迭代器),自己定义一个类,然后定义一个​​__iter__()​​​和​​__next__()​​, 然后这个类实例化的对象就是一个迭代器啦。

但是这样写太麻烦啦!何况我们现在还没有学到类的知识,怎么办?给你一个魔法棒,让你快速优雅高效地造一个迭代器。


yield关键字

第一种自造迭代器的方法就是使用​​yield​​关键字。具体怎么实现呢?

非常简单,如下所示:

def g():
print("Hey~ 生成器")
yield 1

 上面的写法非常类似于函数的定义,相当于把return换成了​​yield​​(当然,并没有这么简单)。

 此时,我执行​​g()​​返回的就是一个生成器。就是这么简单。

ret = g()
print(ret) #输出<generator object g at 0x101fef6d0>

但是这里有个特别需要注意的地方,也是与函数最明显的区别:

我们执行​​g()​​的时候,并没有打印​​"Hey~ 生成器"​​,就像函数没执行一样。

这也是生成器一个非常重要的特点,那就是你执行​​g()​​返回的是一个生成器,同时只有在迭代它(调用它的​​__next__()​​)的时候它才开始执行内部代码,碰到​​yield​​关键字就返回yield后面的值并停止。

print(ret.__next__()) $print(next(ret))

输出:

Hey~ 生成器
1

当然for循环它也是可以的:

for i in ret:
print(i)

 输出:

Hey~ 生成器
1

 ​​yield​​还可以多次执行,这与return也有区别。

def g2():
print("Hey~ 生成器1")
yield 1
print("Hey~ 生成器2")
yield 2

ret = g2()

此时,你执行下​​next(ret)​​​,会打印​​"嘿!生成器1"​​​,然后返回一个​​1​​​,再执行一次​​next(ret)​​​,会打印出​​"嘿!生成器2"​​​,然后返回一个​​2​​。

print(next(ret))
# 输出
Hey~ 生成器1
1
print(next(ret))
# 输出
Hey~ 生成器2
2


yield与return的区别

在一个函数里return只能执行一次,​​return​​之后函数就彻底结束了:

def test_return():
return 1
return 2 #永不执行
return 3 #永不执行

​​yield​​之后可以保存函数的运行状态,下次继续执行:

def test_yield():
yield 1
yield 2 #下次next()后执行
yeild 3 #下次next()后执行

下面的例子中,使用​​return​​​时,只能返回​​0​​。

def test_return2():
for i in range(10):
return i #只能返回0,函数就结束了

使用​​yield​​​能够依次返回​​0~9​​。

def test_yield2():
for i in range(10):
yield i #每调用一次next()就会一次弹出0~9


yield的作用

  • ​​yield​​把函数变成了生成器(生成器就是迭代器)。
  • 为函数封装好了​​__iter__​​​和​​__next__​​方法,把函数的执行结果做成了迭代器。
  • 遵循迭代器的取值方式 —​​obj.__next__()​​​,触发的是函数的执行。函数暂停与继续执行的状态都是由​​yield​​保存的。
  • 倒计时的例子:

    def countdown(n):
    print("倒计时开始")
    while n > 0:
    yield n
    n -= 1
    print("发射")

    分析下面语句的执行过程:

    g = countdown(5)
    print(g.__next__()) # 打印"倒计时开始" 返回5 (此时n=5)
    print(g.__next__()) # 返回4 (此时n=4)
    print(g.__next__()) # 返回3 (此时n=3)
    print(g.__next__()) # 返回2 (此时n=2)
    print(g.__next__()) # 返回1 (此时n=1)
    print(g.__next__()) # 打印"发射" 抛出StopIteration异常(此时n=0)

    调用​​__next__()​​​时函数执行内部代码,到​​yield​​关键字时暂停:

    g = countdown(5)
    print(g.__next__())

     输出:

    倒计时开始
    5

    生成器也是不能后退:

    g = countdown(5)
    print(g.__next__())
    print(g.__next__())
    for i in g:
    print(i)

    输出:

    倒计时开始
    5
    4
    -- for --
    3
    2
    1
    发射

    每调用一次​​countdown(5)​​得到的都是不同的生成器

    for i in countdown(5):
    print(i)

    for i in copuntdown(5):
    print(i)

    输出:

    5
    5

    下面的例子也是一样,每一次print中​​countdown(5)​​​都是一个全新的生成器,所以打印出来的值都是​​5​​。

    print(countdown(5).__next())
    print(countdown(5).__next())
    print(countdown(5).__next())

    输出:

    5
    5
    5


    生成器表达式

    我们之前学过列表推导式,是这样写的:

    >>> [i for i in range(10)]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    这样来得到一个元素数量较小的列表是非常方便的,但是如果要创建一个元素数量巨大的列表,就不那么友好了

    >>> [i for i in range(10000000000)]
    ...

     这个时候只要把​​[]​​​换成​​()​​就把列表推导式 变成了生成器表达式,得到的就是一个生成器对象,就是这么神奇。

    这就是第二种自造迭代器的方法。

    >>> (i for i in range(10))
    <generator object <genexpr> at 0x101fef6d0>

     我们可以直接使用for循环遍历上面得到的生成器:

    >>> for i in (i for i in range(10)):
    ... print(i)
    ...
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

    这样我们就能自信的创建个10000000000元素的生成器,不担心内存会爆了。

    >>> (i for i in range(10000000000))
    <generator object <genexpr> at 0x101fef728>


    最后的总结:

    Python面试题之Python生成器_生成器





    上一篇:SNMP学习笔记之SNMP的安装及Python的调用
    下一篇:没有了
    网友评论