06-07 生成器

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与return的区别
"""
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)
posted @ 2020-03-24 20:08  给你加马桶唱疏通  阅读(144)  评论(0编辑  收藏  举报