生成器可以暂停和恢复的函数,返回一个可迭代的对象。
那为啥我们需要一个生成器了?
生成器不像列表,本质是懒加载的,只在需要时才会生成元素。
所以,当处理大型数据集时,生成器会更加有效。
生成器也是普通函数,仅仅使用yield
语句代替return
而已。
简单的例子:
def my_generator():
yield 1
yield 2
yield 3
print(my_generator)
for i in my_generator():
print(i)
结果:
<function my_generator at 0x7fc138c900e0>
1
2
3
执行生成器函数
使用生成器函数时,需要使用next()
函数来获取下一个值。
当调用next()
第一次时,执行从函数开始到第一个yield
语句的位置,并继续执行直到第一个yield
语句的右边的值被返回。
如果一直调用next()
但已经没有对应的yield
,那么会抛出StopIteration
异常,相当于生成器已经没有数据了,但还在试图获取数据,则直接报错了!
代码:
def countdown(num):
print('Starting')
while num > 0:
yield num
num -= 1
cd = countdown(3)
print(next(cd))
print(next(cd))
print(next(cd))
print(next(cd))
结果:
Starting
3
2
1
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
/var/folders/p9/ny6y7zmj0qdblv697s3_vj040000gn/T/ipykernel_99634/880898684.py in <module>
8 print(next(cd))
9 print(next(cd))
---> 10 print(next(cd))
StopIteration:
生成器用于循环
可以直接用for
进行遍历,直到无元素返回。
代码:
def countdown(num):
print("Starting")
while num > 0:
yield num
num -= 1
cd = countdown(3)
for x in cd:
print(x)
结果:
Starting
3
2
1
生成器用于迭代
我们可以将生成器,类比为列表,使用相关的内置函数如sum
、sorted
一样可以很好的工作。如下代码:
def countdown(num):
print("Starting")
while num > 0:
yield num
num -= 1
cd = countdown(3)
# 直接调用sum
sum_cd = sum(cd)
print(sum_cd)
cd = countdown(3)
# 直接调用sorted
sorted_cd = sorted(cd)
print(sorted_cd)
结果:
Starting
6
Starting
[1, 2, 3]
生成器的优势:节省内存!
既然生成器的值是懒加载的,那么它就可以节省内存。
例如,当需要时,生成器才会需要的数据,后续数据还未开始进行计算处理,所以生成器可以在所有元素生成之前开始使用。
我们通过两个例子,看对比使用与不用生成器是的内存 对比。
不使用生成器
下面代码计算[0-1000000)
的和,但中间使用列表来存储所有数字,如下代码:
def firstn(n):
num, nums = 0, []
while num < n:
nums.append(num)
num += 1
return nums
sum_of_first_n = sum(firstn(1000000))
print(sum_of_first_n)
import sys
print(sys.getsizeof(firstn(1000000)), "bytes")
结果:
499999500000
8697472 bytes
使用生成器
类似上面使用列表,这次我们改为yield
来返回数字,如下代码:
def firstn(n):
num = 0
while num < n:
yield num
num += 1
sum_of_first_n = sum(firstn(1000000))
print(sum_of_first_n)
import sys
print(sys.getsizeof(firstn(1000000)), "bytes")
结果:
499999500000
128 bytes
看到了吗?内存占比只有128 bytes 了?神奇不
实例: 斐波那契数列
斐波那契数列(Fibonacci sequence),又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……。
我们用yield
来返回每个斐波那契数,如下代码:
def fibonacci(limit):
a, b = 0, 1 # 初始值
while a < limit:
yield a
a, b = b, a + b
fib = fibonacci(30)
print(list(fib))
结果:
[0, 1, 1, 2, 3, 5, 8, 13, 21]
生成器表达式
就像列表推导一样,生成器也可以使用同样的语法,只是用圆括号()
而不是方括号[]
。
小心不要混淆它们,因为生成器表达式比列表推导式慢得多,因为它们调用函数的时间开销比列表推导多得多。
示例代码:
import sys
# 生成器表达式
mygenerator = (i for i in range(1000) if i % 2 == 0)
print(sys.getsizeof(mygenerator), "bytes")
# 列表表达式
mylist = [i for i in range(1000) if i % 2 == 0]
print(sys.getsizeof(mylist), "bytes")
结果:
120 bytes
4272 bytes
生成器的概念
我们也可以将类实现为一个可迭代对象, 但必须实现__iter__
和__next__
从而可以跟踪当前状态(这里是当前数字),并且处理StopIteration
。
看下面的代码,可以很好地理解生成器的内部原理:
class firstn:
def __init__(self, n):
self.n = n
self.num = 0
def __iter__(self):
return self
# __next__ 方法
def __next__(self):
if self.num < self.n:
cur = self.num
self.num += 1
return cur
else:
raise StopIteration()
firstn_object = firstn(1000000)
print(sum(firstn_object))
结果:
499999500000
小节
生成器函数,外表看上去像是一个函数,但是没有用return
语句一次性的返回整个结果对象列表,取而代之的是使用yield
语句一次返回一个结果。
生成器函数返回一个迭代器,for
循环等迭代环境对这个迭代器不断调用next
函数,不断的运行到下一个yield
语句,逐一取得每一个返回值,直到没有yield
语句可以运行,最终引发StopIteration
异常。
希望大家能理解到生成器的精髓!
感谢你的阅读。欢迎大家点赞、收藏、支持!
pythontip 出品,Happy Coding!
公众号: 夸克编程
我们的小目标: 让天下木有难学的Python