一 生成器与yield
若函数体包含yield关键字,再调用函数的时候,并不会执行函数体代码,得到的返回值即 生成器表达式
def test(start, stop, step=1):
print("start...")
while start < stop:
yield start
start += step
print("end...")
g = test(0, 3)
print(g) # <generator object test at 0x000002BD360943C0>
# 生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器
print(g.__iter__()) # <generator object test at 0x000001A65FFD43C0>
print(g.__next__()) # 0 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数
print(g.__next__()) # 1 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...
print(next(g)) # 2 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...
print(g.__next__()) # 这一行报错,触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代
# 调用一次g.__next__()等价于调用了一次next(g),会在遇到一个yield的时候,停止下来,然后将前面的内容打印出来。
# 下一次调用的时候,会在停留的位置继续向后执行,直到遇到下一个yield的时候再停下来,然后将遇到这个yield之前的内容打印出来
既然生成器对象属于迭代器,那么必然可以使用for循环迭代,如下:
>>> for i in countdown(3):
... print(i)
...
countdown start
3
2
1
Done!
二 yield表达式应用
有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值。那yield究竟干啥的:
首先吐槽下:网上资源出来的这种,这尼玛你们能看得懂吗,这说的啥哟…
无恶意,只是吐槽下,接下来是我自己的理解,如有错误,评论区见(ps:不过你小心点,我来自祖安,23333)
前面章节函数的基本使用中说过return,那return是干什么的呢,
return就是函数体中的代码运行到return就会终止停下来,不管后面有多少代码,都不会去执行,然后返回return 后面接的东西,这个东西可以是数字,可以是字母,可以是函数等等,没有就是代表None。
所以我们可以先把yield看成是一个“return”,程序运行到这一行也会停下来,比如说
def func():
print(11111)
yield 1
print(22222)
yield 2
print(33333)
g = func() # <generator object func at 0x015B5F08>
res1 = next(g)
res2 = next(g)
print(res1)
print(res2)
"""
执行结果:
11111
22222
1
2
思路:
1 函数名+括号代表函数开始执行了,所以func()程序执行之后,因为在函数中有个yield,所以函数并不会真正的执行,所以在func()之后,得到了一个生成器g
2 直到有next(g)的代码的出现,才会真的执行,而next(g)的底层实现就是调用了生成器g的内置方法__next__()
3 程序执行next(g)之后,就会运行函数func内体代码,直到遇到yield才会停下来,并将遇到yield之前的代码执行完毕,这个例子中就是打印11111
4 如果还有next(g),那么则会在停下来的位置,继续往后执行,直到遇到第二个yield才会停下来,并将遇到第一个yield和第二个yield之间的代码执行完毕,
这个例子中就是22222,第二个yield之后的代码不会运行,print(33333)自然也不会打印
5 yield会将紧跟在yield后面的参数作为,g.__next__()的返回值
"""
那有人就会有疑问了,既然yield会将紧跟在yield后面的参数作为next(g)的返回值,那么yield自身的返回值是什么,看下面代码:
def func():
print(111)
result1 = yield 1
print("result1: ", result1)
print(222)
result2 = yield 2
print(333)
g = func()
res1 = next(g)
print("res1: ", res1)
"""
执行结果:
111
res1: 1
分析:
咦???为什么print("result1: ", result1)这一行没有执行,上面说了,遇到yield会停下来,直到下一个next(g)的出现,才会在停下来的位置,继续往后执行,直到遇到第二个yield才会停下来,并将遇到第一个yield和第二个yield之间的代码执行完毕。
"""
再接着看:
def func():
print(111)
result1 = yield 1
print("result1: ", result1)
print(222)
result2 = yield 2
print(333)
g = func()
res1 = next(g)
res2 = next(g)
print("res1: ", res1)
print("res2: ", res2)
"""
执行结果:
111
result1: None
222
res1: 1
res2: 2
分析:
field的返回值是None
"""
那如何让其返回值不为None呢,就要用到send方法了
def func():
print(111)
result1 = yield 1
print("result1: ", result1)
print(222)
result2 = yield 2
print(333)
g = func()
next(g)
res1 = g.send("a")
print("res1: ", res1)
"""
执行结果:
111
result1: a
222
res1: 2
分析:
send方法前要有个next(g),不然会报错,TypeError: can't send non-None value to a just-started generator,这是报错信息。
那么这个next(g)可以了解为"初始化",让函数在result1=yield 1这一行停下来,等待g.send()为其传参。所以当我们send("a")时,就会为yield 1自身的返回值传入a。
眼尖的炮友们,基友们,女孩子的话就叫女朋友们,就会发现,print("res1: ", res1)这一行的执行结果是res1: 2,而不是1,这是因为,你调用send()方法的时候,给其传参的同时,也在其内部运行了一次__next__()方法
"""
三 三元表达式,列表生成式,生成器表达式
3.1 三元表达式
三元表达式是python为我们提供的一种简化代码的解决方案,语法如下
res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值
例如:
def max2(x, y):
if x > y:
return x
else:
return y
res = max2(1, 2)
# 上述代码用三元表达式可以一行解决
x = 1
y = 2
res = x if x > y else y # 三元表达式
3.2 列表生成式
列表生成式是python为我们提供的一种简化代码的解决方案,用来快速生成列表,
egg_list = []
for i in range(10):
egg_list.append("鸡蛋%s" %i)
# 用列表生成式一行就可以解决
egg_list = ["鸡蛋%s" %i for i in range(10)]
3.3 生成器表达式
创建一个生成器对象有两种方式,一种是调用带yield关键字的函数,另一种就是生成器表达式,与列表生成式的语法格式相同,只需要将[]换成()
对比列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象
>>> [x*x for x in range(3)]
[0, 1, 4]
>>> g = (x*x for x in range(3))
>>> g
<generator object <genexpr> at 0x000001D97509BCF0>
>>> g.__next__()
0
>>>
如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成
with open('db.txt','rb') as f:
nums=(len(line) for line in f)
total_size=sum(nums) # 依次执行next(nums),然后累加到一起得到结果=