Python的生成器
一 生成器与yield
若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象。
>>> def my_range(start,stop,step=1):
... print('start...')
... while start < stop:
... yield start
... start+=step
... print('end...')
...
>>> g=my_range(0,3)
>>> g
<generator object my_range at 0x104105678>
生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器。
>>> g.__iter__
<method-wrapper '__iter__' of generator object at 0x1037d2af0>
>>> g.__next__
<method-wrapper '__next__' of generator object at 0x1037d2af0>
因而我们可以用next(生成器)触发生成器所对应函数的执行,
>>> next(g) # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数
start...
0
>>> next(g) # 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...
1
>>> next(g) # 周而复始...
2
>>> next(g) # 触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代
end...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
既然生成器对象属于迭代器,那么必然可以使用for循环迭代,如下:
>>> for i in countdown(3):
... print(i)
...
countdown start
3
2
1
Done!
有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值。
总结:return和yield的区别?
(1)return:只能返回一次值,就结束函数。
(2)yield:可以返回多次值。
(3)yield:可以将函数暂停到某个位置。
(4)return和yield不能混用。
二 yield表达式应用
# 如何得到自定义的迭代器?
# (1)在函数内存在yield关键字.
# (2)调用函数并不会执行函数体代码.
# (3)会返回一个生成器对象,生成器即自定义的迭代器
def func():
# return # 返回函数内的值,但是只能返回一个值,程序遇到return就会跳出.
print("第一次")
yield 1
print("第二次")
yield 2
print("第三次")
yield 3
print("第四次")
g = func()
print(g)
# 运行结果是: <generator object func at 0x0000025A10E4F660> generator翻译:生成器,其实生成器就是迭代器,只不过是自定义出来的.
# 如何使用这个自定义的生成器?
# g.__iter__()
# print(g.__iter__())
# 会触发函数体代码的运行,然后遇到yield停下来.将yield后面的值当作本次调用的结果返回
# g.__next__()
print(g.__next__()) # 第一次 1
print(g.__next__()) # 第二次 2
print(g.__next__()) # 第三次 3
print(g.__next__()) # 没有返回值之后报错 :StopIteration
# 统计字符串长度
print("aaa".__len__())
len("aaa") # 本质是"aaa".__len__()
next(g) # 本质是g.__next__()
# iter(可迭代对象) 本质可迭代对象.__iter__()
yield表达式的应用(一)
# 简单的理解yield
def func():
print("start....")
x = yield 111 #
print("哈哈哈哈")
yield 222
# print("stop....")
g = func()
res = g.send(None) # 必须先运行一下g.send(None)等同于next(g)
print(res) # 返回值是 111
res = g.send("lsj") #
print(res) # 返回值是 222
"""
start....
111
哈哈哈哈
2222
"""
# yield后不跟返回值
# x = yield返回值(默认返回值为None)一定在函数内的生成器
# 把生成器当作一条狗喂狗投食
def dog(name):
print("道哥%s准备吃东西了..."%name)
while True:
# x = yield None # 不写返回值表示返回值是None
# x拿到的是yield接受到的值
x = yield # 不写返回值表示返回值是None
print("道哥%s吃了%s"%(name,x))
g = dog("alex")
# print(g) # <generator object dog at 0x0000021DC24AF430>
# print(next(g)) # 道哥lsj准备吃东西了 None
# res = next(g)
# print(res) # None
# next(g) # 道哥alex吃了None,遇到yield停下来等待
# 使用send为x传值:也就是seed为yield赋值,yield在传给x
# g.send("一根骨头")
# TypeError: can't send non-None value to a just-started generator
g.send(None) # 等同于next(g)
# 测试g.send(None)等同于next(g)
# g.send(None) # 道哥alex吃了None
# next(g) # 道哥alex吃了None
# 再次传值可以是字符串、可以是类型的数据
g.send("一根骨头") # 道哥alex吃了一根骨头
g.send("肉包子") # 道哥alex吃了肉包子
g.send([1,2,3]) # 道哥alex吃了[1, 2, 3]
# 总结有yield和没有yield的区别?
# (1)有yield的生成器,运行到yield可以暂停。等待传值后在运行。
# (2)没有yield的函数在运行时,不停止一直运行。
# 关闭g
# g.close()
# g.send("1111") # 关闭后再次传值会报错StopIteration
yield表达式的应用(二)
# yield表达式的应用(二)了解
# x = yield 返回值(默认返回值为None)一定在函数内的生成器
# 把生成器当作一条狗喂狗投食
def dog(name):
food_list = [] # 定义一个空列表
print("道哥%s准备吃东西了..."%name)
while True:
# x = yield None # 不写返回值表示返回值是None
# x拿到的是yield接受到的值(值先给yield后给x)
# x = yield 1111 # yield后跟返回值:1111先赋值给yield后把yield赋值给x
x = yield food_list # yield后跟空列表
print("道哥%s吃了%s"%(name,x))
food_list.append(x)
g = dog("alex")
# g.send(None) # 道哥alex准备吃东西了...
# res = g.send(None) # 道哥alex准备吃东西了...
# print(res) # 1111
# 加入send功能x
# res = g.send("肉包子")
# print(res)
#
# res = g.send(["a","b"])
# print(res)
# 向定义的列表内传参
res = g.send(None)
print(res) # []
res = g.send("一根骨头")
print(res) # ['一根骨头']
res = g.send("肉包子")
print(res) # ['一根骨头', '肉包子']
三 三元表达式、列表生成式、生成器表达式
- 三元表达式
# 需求: 比较两个参数的大小,返回较大值
# def func(x,y):
# if x > y:
# return x
# else:
# return y
#
# res = func(1,2)
# print(res)
# 使用三元表达式,解决上面的需求
# 语法格式:条件成立时返回的值 if 条件 else 条件不成立时返回的值
x = 1
y = 2
res = x if x > y else y
print(res)
- 列表生成式
- 列表生成式是python为我们提供的一种简化代码的解决方案,用来快速生成列表,语法如下
[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN
]
l = ['lsj','lxx_dsb','alex_dsb','wxx','xxq_dsb']
# 需求:将含有dsb放到新列表里
# new_l = []
# for name in l:
# if name.endswith('dsb'):
# new_l.append(name)
# print(new_l)
# 列表生成器的目的是将上述代码更精简
# new_l = [name for name in l if name.endswith('dsb')] # ['lxx_dsb', 'alex_dsb', 'xxq_dsb']
# new_l = [111 for name in l if name.endswith('dsb ')] # [11, 11, 11]
new_l = [name for name in l ] # ['lsj', 'lxx_dsb', 'alex_dsb', 'wxx', 'xxq_dsb']
print(new_l)
# 案例
# 把所有小写字母全变成大写
new_l = [name.upper() for name in l ]
print(new_l)
# 把所有的名字去掉后缀_dsb
# new_l = [name.split('_')[0] for name in l ] # 方式一
new_l = [name.replace('_dsb','') for name in l ] # 方式二
print(new_l)
- 生成器表达式
l = [表达式 for x in 可迭代对象 if 条件]
g = (表达式 for x in 可迭代对象 if 条件) next(g)
sum(表达式 for x in 可迭代对象 if 条件)
list(表达式 for x in 可迭代对象 if 条件)
dic = {键:值 for k in 可迭代对象 if 条件}
set = {元素 k in 可迭代对象 if 条件}
# (1)字典生成式
keys = {'name','age','gender'}
dic = {key:None for key in keys}
print(dic,type(dic)) # {'age': None, 'gender': None, 'name': None} <class 'dict'>
# 把下面的列表转换成字典,并且不要gender的数据
items =[('name','lsj'),('age',18),('gender','male')]
dic = {k:v for k,v in items if k != 'gender'}
print(dic,type(dic)) # {'name': 'lsj', 'age': 18} <class 'dict'>
# (2)集合生成式
set = ['name','age','gender']
set1 = {k for k in set}
print(set1,type(set1)) # {'age', 'name', 'gender'} <class 'set'>
# (3)元组生成式,从0到10一组数
g = (i for i in range(10) if i > 3)
print(g,type(g)) # 得到不是元组(因为元组式不可变数据类型),所以没有元组生成式,这是一个生成器表达式.
# <generator object <genexpr> at 0x0000022B79C4F7B0> <class 'generator'>
# (4)针对生成器的玩法?
ge = (i for i in range(10) if i > 3)
# 强调:此刻ge内部一个值也没有,没等到next(ge)的时候取出一个值,再next(ge)的时候再取出一个值
print(ge)
print(next(ge)) # 4
print(next(ge)) # 5
# 应用:统计笔记.txt文件里的字符数 sum()
# print(sum([1,2,3,4,5])) # sum()对传入的可迭代对象进行累加,运行结果是:15
# 方式一:
# with open('笔记.txt',mode='rt',encoding='utf-8') as f:
# res = 0
# for line in f:
# res += len(line)
# print(res)
# 方式二:如果文件行过多,此时文件内容放入f的数据太多
# with open('笔记.txt',mode='rt',encoding='utf-8') as f:
# size_of_line = [len(line) for line in f]
# print(size_of_line) # 每一行的长度 [11, 1]
# res = sum(size_of_line)
# print(res)
# 方式三:效率最高,不受方式二文件行数过多影响
# with open('笔记.txt',mode='rt',encoding='utf-8') as f:
# g = (len(line) for line in f)
# print(g)
# res = sum(g)
# print(res)
# 方式三:优化
with open('笔记.txt',mode='rt',encoding='utf-8') as f:
# res = sum((len(line) for line in f)) # 运行结果: 10
res = sum(len(line) for line in f) # 同上的效果 10
print(res)