python——生成器

生成器实际上就是一个函数,称为生成器函数,与普通函数的区别是,普通函数在函数执行完毕后会通过return关键字返回函数执行的结果,但生成器函数在执行完毕后,会通过yield关键字进行返回,并且返回以后,这个函数并不会从内存中清理掉,而是会阻塞在yield语句处,等待下一次调用。

def gen(n):
    m = n + 1
    print("lalala")
    yield m
    print("hehehe")

g = gen(3)
print(g)
print(g.__next__())
print(g.__next__())

执行结果:

Traceback (most recent call last):
  File "E:\Python\PythonBasis\生成器\generator01.py", line 10, in <module>
    print(g.__next__())
StopIteration
<generator object gen at 0x000001EEEC2D6E30>
lalala
4
hehehe

从以上代码及函数执行结果可以看到:

  • 生成器函数在调用执行后会返回一个生成器对象
  • 生成器函数在被调用时,函数体中的代码只会执行到yield语句之前,yield之后的语句不会执行
  • 生成器对象通过g.__next__()方法去获取生成器的生成值,当所有的yield语句执行完毕后(也即函数自上而下执行完毕后),再一次调用g.__next__方法就会抛出StopIteration异常

因此,当你想要用生成器生成多个值时,有两种方式去实现:
方式一:在生成器函数中编写多个yield语句,如下:

#!coding:utf8
def gen():
    print('lalala')
    yield '春'
    print("lalala")
    yield '夏'
    print("lalala")
    yield '秋'
    print("lalala")
    yield '冬'
    print("lalala")

g = gen()
print(g.__next__())  # 春
print("--------------------")  
print(g.__next__())  # 夏
print("--------------------")
print(g.__next__())  # 秋
print("--------------------")
print(g.__next__())  # 冬
print("--------------------")
print(g.__next__()) # 该语句已没有对应的yield值了可以获取了

以上代码执行结果:

lalala
春
--------------------
lalala
夏
--------------------
lalala
秋
--------------------
lalala
冬
--------------------
lalala
Traceback (most recent call last):
  File "E:\Python\PythonBasis\生成器\generator02.py", line 22, in <module>
    print(g.__next__())
StopIteration

方式二: 通过在生成器函数中编写循环体,将yield语句放到循环体中从而可以实现可以yield多个值。

#!coding:utf8
import random


def gen(n):  # n表示抽奖次数
    print("抽奖开始!")
    thanks = ["谢谢惠顾!" for i in range(7)]
    awards = ["一等奖:劳斯莱斯", "二等奖:保时捷", "三等奖:自行车"]
    awards.extend(thanks)
    # print(awards)
    for i in range(n):
        yield random.choice(awards)


g = gen(3)
# print(g.__next__())
# print(g.__next__())
# print(g.__next__())
# print(g.__next__())

# 通常我们通过迭代的方式去获取生成器中的值
for i in range(3):
    print(g.__next__())

以上代码的执行结果如下:

抽奖开始!
谢谢惠顾!
谢谢惠顾!
一等奖:劳斯莱斯

二、生成器的使用场景

1. 读取大文件

以下代码演示通过生成器 将一个超大文件拷贝到另一个文件中,而不会引发内存溢出问题

# 逐行读取文件的生成器函数
def my_read(filename):
    f = open(filename)  # <class '_io.TextIOWrapper'>, 是一个可迭代对象
    for row in f:
        yield row


if __name__ == '__main__':
    file = my_read("./a.txt")  # 获取文件中每一行的生成器对象
    with open('./b.txt', 'w') as fw:
        while True:
            try:
                row = file.__next__()
                fw.write(row)  # 将该行写入到新文件
            except:
                break  # 如果捕获到异常就退出循环
2. 生成无限序列

对于无限序列,我们是不可能将整个序列存储在内存中的,但是如果我们只是想逐个的去消费这个序列中的每个值,那就可以采用生成器,以下以斐波拉契数列为例,假如我想逐个去消费斐波拉契数列中的每一项,可以采用以下方式:

# 1, 1, 2, 3, 5, 8, 13...
def Fibonacci():  # n>0
    first = 1
    yield first
    second = 1
    yield second
    while True:
        cur = first + second
        yield cur
        first = second
        second = cur


fibo = Fibonacci()

for i in range(10):  # 获取斐波那契数列的前10项
    print(fibo.__next__(), end=' ')

以上代码的执行结果:

1 1 2 3 5 8 13 21 34 55 
3. 优化内存

假设当前你需要一个1-10000的序列,下面对比一下分别使用列表和生成器两种方式去保存它,并对比两种方式的内存消耗。

import sys

def gen():
    for i in range(10000):
        yield i

g = gen()  # 生成器
lst = [x for x in range(10000)]  # 列表

print(sys.getsizeof(g))
print(sys.getsizeof(lst))

以上代码执行结果:

104
85176

这其实很好理解,以数列为例,生成器其实只是存储了这个数列的通项公式, 而列表却是存储了数列的所有值,占用内存自然会大许多

另外, 与列表推导式类似, 生成器还有另外一种实现方式, 叫做 生成器表达式

my_gen = (x for x in range(10000))

三、生成器进阶

1. yield from 语句

假设,我已经定义了一个生成0-3序列的生成器和一个生成3-10序列的生成器。现在需求变更,我想要一个生成0-10序列的生成器,那么我就可以新定义一个生成器函数,将原来的生成器函数代码各自拷贝一份,放置到新的生成器函数体中。但是这种做法很显然太麻烦,不够pythonic,所以在python3.3以后cpython为我们提供了yield from语句,当你想要把多个生成器组合起来作为一个生成器时,可以试用yield from语句

def gen01():
    for i in range(3):
        yield i


def gen02():
    for i in range(3, 10):
        yield i


gen_01 = gen01()
gen_02 = gen02()


def gen(g1, g2):
    yield from g1
    yield from g2


g = gen(gen_01, gen_02)
for i in range(10):
    print(g.__next__(), end=' ')

以上代码执行结果:

0 1 2 3 4 5 6 7 8 9 
2. send()方法

由于生成器在两次yield之间会阻塞程序,因此,我们其实是可以它阻塞期间向它传递一些值,从而改变生成器函数中原本的控制逻辑。这种传递值的方式就是send()方法。
以下演示通过生成器send()方法可以向生成器函数内部传值的特性,我们可以实现一个密码生成器,这个密码可以从函数外部通过send方法控制当前想要生成哪种形式的密码。

def gen_password():
    '''
    密码生成器
    :return:
    '''
    flag = "digit"
    while True:
        # 默认生成6位数字密码
        while flag == "digit":
            tmp = yield ''.join(random.sample(string.digits, 6))
            flag = flag if not tmp else tmp
        while flag == "letter":  # 生成6位大小写混合的密码
            tmp = yield ''.join(random.sample(string.ascii_letters, 6))
            flag = flag if not tmp else tmp
        while flag == "lower":
            tmp = yield ''.join(random.sample(string.ascii_lowercase, 6))
            flag = flag if not tmp else tmp
        while flag == "upper":
            tmp = yield ''.join(random.sample(string.ascii_uppercase, 6))
            flag = flag if not tmp else tmp

g = gen_password()

print(g.__next__())
g.send("letter")
print(g.__next__())
g.send("upper")
print(g.__next__())
print(g.__next__())
g.send("lower")
print(g.__next__())
print(g.__next__())
print(g.__next__())

以上代码执行结果如下:

685140
yPFMzQ
TYGECW
CXAVYL
cordbp
tbfmkh
ipvhtf
3. throw()方法主动抛出异常

throw()方法允许生成器对象向外抛出一个异常

def gen():
    i = 0
    while True:
        yield i
        i += 1

g = gen()

for i in g:
    print(i, end=' ')
    if i > 9:
        g.throw(ValueError("The number is greater than 10!"))
    i += 1

以上代码执行结果:

Traceback (most recent call last):
  File "E:\PythonBasics\生成器\gen04.py", line 12, in <module>
    g.throw(ValueError("The number is greater than 10!"))
  File "E:\PythonBasics\生成器\gen04.py", line 4, in gen
    yield i
ValueError: The number is greater than 10!
0 1 2 3 4 5 6 7 8 9 10 
4. close()方法停止生成器

执行生成器的close方法以后,生成器就废掉了,再也不能生成任何元素了

def gen():
    i = 0
    while True:
        yield i
        i += 1

g = gen()

for i in g:
    print(i, end=' ')
    if i > 9:
        g.close()

print(g.__next__())

以上代码执行结果:

0 1 2 3 4 5 6 7 8 9 10 Traceback (most recent call last):
  File "E:\PythonBasics\生成器\gen05.py", line 14, in <module>
    print(g.__next__())
StopIteration

以上就是本人关于python生成器的理解,如果一些地方理解有问题,万望指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值