02 生成器
一. 生成器与yield
"""
如何得到自定义的迭代器: 生成器 generator
在函数内定义yield关键字, 调用函数并不会执行完函数体代码, 会返回一个生成器对象(生成器就等同于迭代器).
注意:
一旦函数体内出现yield关键字, 再去调用函数, 就和函数体代码没有关系了, 得到的就是一个生成器, 你的函数体代码的生效, 取决与你next.
"""
# 示例:
def func():
yield 1
yield 2
yield 3
res_generator = func()
# 返回一个生成器对
print(res_generator) # <generator object func at 0x00000246ECEF42E0>
# 生成器就等同于迭代器, 也有迭代器下的内置方法__iter__(),__next__(), 也有iter()和next()功能
res_generator.__iter__()
iter(res_generator)
# res_generator.__next__()
# next(res_generator)
# 对定义yield关键字的函数,返回的生成器对象迭代取值
res1 = res_generator.__next__()
print(res1) # 1
res2 = res_generator.__next__()
print(res2) # 2
res3 = res_generator.__next__()
print(res3) # 3
# 补充: 之前我们使用的len()功能, 内部其实最后调用的还是内置的__len__()方法
res = len('aaa')
print(res) # 3
res = 'aaa'.__len__()
print(res) # 3
# 需求(实例): 实现range功能
def my_range(start, stop, step=1):
print("start...")
while start < stop:
yield start
start += step
print("stop...")
res = my_range(0, 10)
print(res)
for item in res:
print(item) # <generator object my_range at 0x00000142F62142E0>
'''
start...
0
1
2
3
4
5
6
7
8
9
stop...
'''
"""
yield存在的意义:
有了yield关键字, 就有了一种自定义迭代器的实现方式.
返回值的次数:
yield可以返回多次值, return只能返回一次值
对函数的控制及返回值:
yield可以用于返回值, 可以保存函数的运行状态并挂起函数.
return只要一遇到, 函数就结束了.
"""
二. yield表达式应用
1. yield基本使用:next,send,close
'''
yield流程分析:
1. 函数体代码中有yield关键字, 调用该函数返回的值是一个生成器对象(生成器==迭代器).
2. 这个时候函数体代并不会执行.需要初始化: g.send(None) 或者 next(g) 其中next(g)等同于g.send(None).
3. 初始化以后函数体代码会执行, 直到执行到yield关键字, 函数挂起在当前位置. 如果yield右边有值, 那么会把右边指定的值充当返回值. 如果没有值, 默认返回值的是None.
4. 当使用g.send(值), send会把值传递给yield关键字,赋值给food. 然后send会执行等同于next的功能继续迭代执行下面代码.
5. 直到再次遇见yield关键字, 函数就又会被挂起在当前位置, 同时yield返回右边的值, 当然返回值也同步骤3所说的一样.
6. 当使用g.close(), 会关闭这个生成器对象g可以继续迭代取值的可能.
7. 关闭以后继续使用g.send(值)或者说next(g)无法传值, 且会抛出异常: StopIteration
'''
def eater():
print("Ready to eat")
while True:
food = yield
print(f"Get the food: {food} and start to eat")
# 1. 函数体代码中有yield关键字, 调用该函数返回的值是一个生成器对象(生成器==迭代器).
g = eater()
# 2. 这个时候函数体代并不会执行.需要初始化: g.send(None) 或者 next(g) 其中next(g)等同于g.send(None).
res = next(g) # Ready to eat
# 3. 初始化以后函数体代码会执行, 直到执行到yield关键字, 函数挂起在当前位置. 如果yield右边有值, 那么会把右边指定的值充当返回值. 如果没有值, 默认返回值的是None.
print(res) # None
# 4. 当使用g.send(值), send会把值传递给yield关键字,赋值给food. 然后send会执行等同于next的功能继续迭代执行下面代码.
res = g.send('热狗') # Get the food: 热狗 and start to eat
# 5. 直到再次遇见yield关键字, 函数就又会被挂起在当前位置, 同时yield返回右边的值, 当然返回值也同步骤3所说的一样.
print(res)
g.send('鸡蛋') # Get the food: 鸡蛋 and start to eat
g.send(None) # Get the food: None and start to eat
next(g) # Get the food: None and start to eat
# 6. 当使用g.close(), 会关闭这个生成器对象g可以继续迭代取值的可能.
g.close()
# 7. 关闭以后继续使用g.send(值)或者说next(g)无法传值, 且会抛出异常: StopIteration
g.send(None)
2. yield+装饰器应用: 使用装饰器完成所有表达式形式的yield对应生成器的初始化操作
def init_yield(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
next(res) # res.send(None)
return res
return wrapper
@init_yield # eater = wrapper = init_yield(eater)
def eater():
print("Ready to eat")
while True:
food = yield
print(f'Get the food: {food} and start to eat')
g = eater() # Ready to eat
g.send('热狗') # Get the food: 热狗 and start to eat
g.send('鸡蛋') # Get the food: 鸡蛋 and start to eat
3. yield高级用法(挂起+返回值): 表达式形式的yield可以用于返回多次值, 用法: 变量名=yeild 值
# 示例一:
def eater():
li = []
while True:
x = yield li
li.append(x)
g = eater()
res = g.send(None)
print(res) # []
print(g.send(1)) # [1, ]
print(g.send(2)) # [1, 2]
print(g.send(3)) # [1, 2, 3]
print(g.send(4)) # [1, 2, 3, 4]
# 示例二:
def eater():
print("Ready to eat")
food_list = []
while True:
print(food_list)
food = yield food_list
food_list.append(food)
g = eater() # []
next(g) # yield初始化
g.send("鸡腿") # ['鸡腿']
g.send("鸭腿") # ['鸡腿', '鸭腿']
g.send('牛肉') # ['鸡腿', '鸭腿', '牛肉']
4. yield高级用法分析: yield挂起状态返回返回值 + yield的send状态继续迭代然后赋值
# send做了2件事: 传值给yield, next迭代, yield赋值给x
def func():
print('start.....')
x = yield 1111
print('哈哈哈啊哈1:', x)
y = yield 2222
print('哈哈哈啊哈2:', y)
g = func()
res = next(g)
print(res)
res_x = g.send(3333)
print(res_x)
res_y = g.send(4444)
print(res_y)
'''
# 打印结果
start.....
111
哈哈哈啊哈1: 3333
2222
哈哈哈啊哈1: 4444
'''
三. 三元表达式
# 伪代码示例:
"""
res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值
"""
# 需求: 定义一个比较2个值大小的函数
# 1. 基本实现
def max2(x, y):
if x > y:
return x
else:
return y
res = max2(10, 20)
print(res) # 20
# 2. 优化: 使用三元表达式
def max2(x, y):
return x if x > y else y
res = max2(10, 20)
print(res) # 20
四. 生成式
1. 列表生成式
'''
英文伪代码示例:
[expression
for item1 in iterable1 if codition1
for item2 in iterable2 if codition2
...
for itemn in iterablen if coditionn]
中文伪代码示例:
[表达式
for 元素1 in 可迭代对象1 if 条件1
for 元素2 in 可迭代对象1 if 条件2
...
for 元素n in 可迭代对象1 if 条件n]
'''
# 需求: 快速生成一个列表 鸡蛋1 到 鸡蛋10, 且排除鸡蛋7
# 1. 使用for循环
egg_list = []
for i in range(1, 11):
if i == 7:
continue
egg_list.append(f'鸡蛋:{i}')
print(egg_list) # ['鸡蛋:1', '鸡蛋:2', '鸡蛋:3', '鸡蛋:4', '鸡蛋:5', '鸡蛋:6', '鸡蛋:8', '鸡蛋:9', '鸡蛋:10']
# 2. 优化: 使用列表生成式
egg_list = [f'鸡蛋:{i}' for i in range(1, 11) if i != 7]
print(egg_list) # ['鸡蛋:1', '鸡蛋:2', '鸡蛋:3', '鸡蛋:4', '鸡蛋:5', '鸡蛋:6', '鸡蛋:8', '鸡蛋:9', '鸡蛋:10']
li = ['alex_dsb', 'lxx_dsb', 'wxx_dsb', 'ooo', "xxq_dsb", 'egon_xxx']
# 把后缀_dsb取出生成一个新的列表
res = [item for item in li if item.endswith('_dsb')]
print(res) # ['alex_dsb', 'lxx_dsb', 'wxx_dsb', 'xxq_dsb']
# 把所有小写字母全变成大写
res = [item.upper() for item in li]
print(res) # ['ALEX_DSB', 'LXX_DSB', 'WXX_DSB', 'OOO', 'XXQ_DSB', 'EGON_XXX']
# 把所有的名字去掉后缀_dsb
# 方式一: 使用replace
# res = [item.replace('_dsb', '') for item in li]
# 方式二: 使用split+索引取值
res = [item.split("_")[0] for item in li]
print(res) # ['alex', 'lxx', 'wxx', 'ooo', 'xxq', 'egon']
2. 集合生成式
# 需求: 根据li中的内容, 排除末尾有'x'的, 快速生成一个集合
li = ['alex', 'lxx', 'wxx', 'ooo', 'xxq', 'egon']
set1 = {item for item in li if not item.endswith('x')}
print(set1) # {'xxq', 'egon', 'ooo'}
3. 字典生成式
# 需求: 更具li, 快速生成一个字典, 字典value默认为None
li = ['name', 'age', 'gender']
dic = {key: None for key in li}
print(dic) # {'name': None, 'age': None, 'gender': None}
# 需求: 根据items, 排除某一项二元组中含有'gender'的二元组. 二元组第一个值作为字典的key, 第二个值作为字典的value
items = [('name', 'egon'), ('age', 18), ('gender', 'male')]
dic = {key: value for key, value in items if key != 'gender'}
print(dic) # {'name': 'egon', 'age': 18}
五. 生成器表达式
'''
!!!!!!!强调!!!!!!!!!:
我们自定义, 从而看到的生成器, 此刻内部一个值也没有。
# 创建生成器对象的2种方式:
1. 调用带yield关键字的函数
2. 生成器表达式, 与生成式的语法相同, 只需要将[]换成()
# 生成器表达式与列表生成式对比
返回值:
列表生成式返回的是一个列表
生成器表达式返回的是一个生成器对象
内存占用:
列表生成式中的元素值, 直接保存在内存
生成器表达式在内存中一次只产生一个值, 比列表生成式更加节省内存空间
'''
# 需求: 生成一个含有4~9之间的生成器对象
g = (i for i in range(4, 10))
print(g) # <generator object <genexpr> at 0x00000151FFBF43C0>
print(next(g)) # 4
print(next(g)) # 5
print(next(g)) # 6
print(next(g)) # 7
print(next(g)) # 8
print(next(g)) # 9
print(next(g)) # 生成器取值完毕, 抛出异常: StopIteration
# 应用场景: 读取大文件的字节数
with open('db.txt', 'rb') as f:
total_bytes = (len(line) for line in f)
total_size = sum(total_bytes)
# 需求: 读取打文件的字符串的个数
with open("db.txt", 'rt', encoding='utf-8') as f:
# 方式一: 定义初始值 + 累加
init_str = 0
for line in f:
init_str += len(line)
# 方式二: 使用生成式表达式 + sum
total_str = sum([len(line) for line in f])
# 方式二优化一: 使用生成器表达式 + sum
total_str1 = sum((len(line) for line in f))
# 方式二优化二: 使用生成器表达式 + sum使用生成器表达式不加括号
total_str2 = sum(len(line) for line in f)