❝Python入门第二十七课,主要是学习了生成器,生成器通过yield实现惰性求值,按需生成数据,有效节省内存并支持高效迭代。
学前提示:请务必先阅读了解『迭代器』后,再阅读本篇文章。因为生成器对象本身就是一种特殊的迭代器。
➊ 生成器函数:函数体中如果出现了yield关键字,那该函数是『生成器函数』。不管能否执行到yield所在的位置,只要函数中有yield,那该函数就是『生成器函数』。
➋ 生成器对象:调用『生成器函数』时,其函数体不会立刻执行,而是返回一个『生成器对象』。
defdemo(): print('demo函数开始执行了……') print(100)yield a = 200 print(a)d = demo()print(d) # <generator object demo at 0x000001FC96545B40>写在『生成器函数』中的代码,需要通过『生成器对象』来执行:
❏ 调用『生成器对象』的__next__()方法,会让『生成器函数』中的代码开始执行。
❏ 当『生成器函数』中的代码开始执行后,遇到yield会“暂停”,并会记录“暂停”的位置。
❏ 后续调用__next__()方法时,都会从上一次“暂停”的位置,继续运行,直到再次遇到yield。
❏ 遇到return会抛出StopIteration异常,并将return后面的表达式,作为异常信息。
❏ yield后面所写的表达式,会作为本次__next__()方法的返回值。
defdemo(): print('demo函数开始执行了') print(100)yield'我是第一个yield所返回的数据' a = 200 print(a)yield'我是第二个yield所返回的数据' b = 300 print(b)return'生成器异常'd = demo()r1 = next(d)print(r1)r2 = next(d)print(r2)try: next(d)except StopIteration as e: print(e)# 运行结果:# demo函数开始执行了# 100# 我是第一个yield所返回的数据# 200# 我是第二个yield所返回的数据# 300# 生成器异常生成器对象是一种特殊的迭代器(本质是通过yield自动实现了迭代器协议)。
defdemo(): print('demo函数开始执行了') print(100)yield'我是第一个yield所返回的数据' a = 200 print(a)yield'我是第二个yield所返回的数据' b = 300 print(b)return'生成器异常'd = demo()# 验证:生成器对象d,和迭代器一样,也拥有:__iter__ 和 __next__ 方法print(hasattr(d, '__iter__')) # Trueprint(hasattr(d, '__next__')) # True# 验证:生成器对象的__iter__方法,和迭代器一样,返回的也是自身result = iter(d)print(result == d) # True# for循环遍历生成器for item in d: print(item)# for循环背后的逻辑d2 = demo()gen = iter(d2)whileTrue:try: value = next(gen) print(value)except StopIteration:breakyield也能写在循环里。
defcreate_car(total):for index in range(1, total + 1):yieldf'下线了第{index}台车'# 创建迭代器对象cars = create_car(5)print(cars)# 调用一次cars的__next__方法,就会得到一台车c1 = next(cars)print(c1)c2 = next(cars)print(c2)c3 = next(cars)print(c3)c4 = next(cars)print(c4)c5 = next(cars)print(c5)# 第6次执行就会抛出StopIteration异常# c6 = next(cars)# print(c6)# 使用for循环遍历cars2 = create_car(5)for car in cars2: print(car)yield from能把一个『可迭代对象』里的内容依次yield出去。(替代:for循环 + yield)
defdemo(): nums = [10, 20, 30, 40, 50]yieldfrom numsd = demo()r1 = next(d)print(r1)r2 = next(d)print(r2)r3 = next(d)print(r3)r4 = next(d)print(r4)r5 = next(d)print(r5)d2 = demo()for x in d2: print(x)使用生成器.send(值)可以让生成器继续执行的同时,给上一次yield传值。
❝注意:
next只能取值,send既能取值,也能送值。第一次启动生成器,不能传值!(或者说只能传None值)
defdemo(): print('demo函数开始执行了') print(100) a = yield'我是第一个yield所返回的数据' print(f'_ {a}') b = yield'我是第二个yield所返回的数据' print(f'_ {b}')return'生成器异常'd = demo()r1 = next(d)print(r1)r2 = d.send(666)print(r2)try: d.send(888)except StopIteration as e: print(e)# 运行结果:# demo函数开始执行了# 100# 我是第一个yield所返回的数据# _ 666# 我是第二个yield所返回的数据# _ 888# 生成器异常➊ 用生成器实现遍历Person类的实例对象:
classPerson:def__init__(self, name, age, gender, address): self.name = name self.age = age self.gender = gender self.address = address self.__attr = [name, age, gender, address]def__iter__(self):# yield self.name# yield self.age# yield self.gender# yield self.addressyieldfrom self.__attrp1 = Person('远方', 41, '男', '山西太原')for attr in p1: print(attr)# 运行结果:#远方#41#男#山西太原➋ 用生成器实现斐波那契数列:
deffibo(total): pre = 1 cur = 1for index in range(total):if index < 2:yield1else: value = pre + cur pre = cur cur = valueyield valuef1 = fibo(10)for item in f1: print(item)# 运行结果:# 1# 1# 2# 3# 5# 8# 13# 21# 34# 55无论是迭代器,还是生成器对象,都可以用list、tuple、set等直接拿到其里面的所有内容(注意:如果数据量很大,可能会挤爆内存)。
deffibo(total): pre = 1 cur = 1for index in range(total):if index < 2:yield1else: value = pre + cur pre = cur cur = valueyield valuef1 = fibo(10)result = tuple(f1)print(result) # (1, 1, 2, 3, 5, 8, 13, 21, 34, 55)生成器表达式:一种用类似列表推导式的语法,快速创建生成器对象的方式。
语法格式:(表达式 for 变量 in 可迭代对象)
什么时候适合用生成器表达式?—— 当“每个结果,只依赖当前这一个元素”时。
nums = [10, 20, 30, 40, 50]# 列表推导式result1 = [n * 2for n in nums]print(result1)# 生成器表达式和列表推导式很像,不要混淆result2 = (n * 2for n in nums)print(result2) # <generator object <genexpr> at 0x000002899789A8E0>for n in nums: print(n)