Python生成器

什么是生成器

通过列表推导式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的 list,从而节省大量的空间。

通俗的理解:
在Python中,这种一边循环一边计算的机制,称为生成器:generator
只记录生成数据的方式(算法),而不事先生成且存储这些数据

生成器

其实利用迭代器也可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。

但在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合 next()函数进行迭代使用。
简而言之,迭代器是可以实现在循环的过程中生成数据的,但有点复杂。
一个具有迭代器功能,且比它更加简单的方式:生成器(generator)

生成器是一类特殊的迭代器

创建生成器的方法1

第一种方法:把一个列表生成式的 [ ] 改成 ( )

nums = [x for x in range(5)]	# 列表
print(type(nums))
print(nums)
print('-' * 20)
nums2 = (x for x in range(5))	# 生成器
print(type(nums2))
print(nums2)

可以直接打印出列表nums的每一个元素,而对于生成器nums2,可以按照迭代器的方法来使用,即可以通过next()函数、for循环、list()等方法使用

创建生成器的方法2

下面使用迭代器来实现一个不确定个数的斐波那契数列:

class FibIterator(object):
    def __init__(self):
        self.num1 = 1   # 前一个数,初始值为数列的第一个数
        self.num2 = 1   # 后一个数,初始值为数列的第二个数

    def __next__(self):
        temp_num = self.num1
        self.num1, self.num2 = self.num2, self.num1 + self.num2

        return temp_num

    def __iter__(self):
        return self


fib = FibIterator()

print(next(fib))    # fib是迭代器,所以不必使用iter(),直接next()就行
print(next(fib))
print(next(fib))

下面使用生成器实现斐波那契数列:

def fib_generator():
	num1 = 1
	num2 = 1
	while True:
		temp_num = num1
		num1, num2 = num2, num1 + num2
		yield temp_num

fib = fib_generator()
print(fib)	# <generator object fib_generator at 0x7f86844e57b0>

num = next(fib)
print(num)

在使用生成器实现的方式中,将原本在迭代器 next 方法中实现的基本逻辑放到一个函数中实现,但是将每次迭代返回数值的 return换成了 yield,此时新定义的函数便不再是函数,而是一个生成器了
即:只要在 def函数中有 yield关键字的 就称为生成器

此时按照调用函数的方式( fib = fib_generator() )就不再是执行函数体了,而是会返回一个生成器对象,然后就可以按照使用迭代器的方式来使用生成器了

yield关键字

1.只要有yield关键字,那么虽然看上去是调用函数,实际上已经变成了创建一个生成器对象
2.通过next调用生成器,可以让这个带有yielddef代码块,开始执行

i.如果是第一次执行,则从 def 代码块的开始部分执行,直到遇到 yield 为止,并且把 yield 关键字后的数值返回,当作 next() 的返回值
ii.如果不是第一次执行,则从上一次暂停的位置执行(即从上一次 yield 关键字的下一个语句开始执行),直到遇到下一次 yield 为止,并且把 yield 关键字后的数值返回,当作 next() 的返回值

获取return的值

def fib_generator():
    num1 = 1
    num2 = 1
    temp_num = num1
    num1, num2 = num2, num1 + num2
    yield temp_num

    temp_num = num1
    num1, num2 = num2, num1 + num2
    yield temp_num

    temp_num = num1
    num1, num2 = num2, num1 + num2
    yield temp_num

    return "end"


fib = fib_generator()
print(next(fib))
print(next(fib))
print(next(fib))	

# 1.如果在调用next的时候,从上一次暂停的位置继续向下运行时,遇不到yield,就会产生异常
# 2.如果在调用next时,接下来没有遇到yield,而是遇到return,那也会产生异常,并且把return的数值用异常暂时存储起来
# print(next(fib))

tryprint(next(fib))
except StopIeration as ret:
	print(ret.value)	# 如果生成器中有return,那么python已经将它封装到了异常对象信息中的value属性

总结:调用 generator时,发现拿不到 generator 的 return语句的返回值。如果想要拿到返回值,必须捕获 StopIteration错误,返回值包含在 StopIteration 的 value中

使用 send 唤醒

除了可以使用 next() 函数来唤醒,让生成器继续执行外,还可以使用 send() 函数来唤醒执行。
使用 send() 函数的一个好处是:可以在唤醒的同时向断点处传入一个附加数据
相同点:

都会让生成器继续向下执行
如果运行时,遇不到yield,都会产生异常

不同点:

next只会让运行继续开始。而send除了可以让其开始运行之外,还可以将某个数据携带过去
def generator_test():
    while True:
        print("-----1")
        num = yield 100
        print("-----2", "num=", num)

g = generator_test()

print(next(g))
print(next(g))
print(next(g))

在这里插入图片描述

使用send:

def generator_test():
    while True:
        print("-----1")
        num = yield 100
        print("-----2", "num=", num)

g = generator_test()

print(next(g))
print(g.send(11))
print(g.send(22))

在这里插入图片描述

一个例子

需求:

1.不确定个数的 点坐标
2.第1次点坐标的x值是0,得到y值1;将此次的y当作第2次点的x坐标值
3.即 第2次点的坐标值是1,得到y值3;将此次的y当作第3次点的x坐标值
4.即 第3次点的坐标值是3,得到y值7;将此次的y当作第4次点的x坐标值
... 以此类推 ...
5.在不确定第几次时,可能需要修改方程(y=2x+1)组中的2与1的值
def create_point():
    x = 0
    k = 2
    b = 1
    while True:
        # y = 2 * x + 1
        y = k * x + b
        temp = yield (x, y)
        if temp:
            k, b = temp
        x = y

fib = create_point()

print(next(fib))
# print(next(fib))

print(fib.send((3, 2)))
print(next(fib))

创建多个生成器

注意:

如果一个def代码块中有yield,那么看上去调用这个def代码块实际上是创建一个新的生成器。
如果调用了多次这个def代码块,意味着创建了多个生成器,且每个生成器之间没有任何关系,各用各的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值