列表推导式(语法糖)
初看列表推导式可能会一头雾水,对词语法的可读性表示质疑,一旦理解语法糖后,你就会感觉出他那简短语句中的魅力。
使用方法
[迭代元素处理 for 迭代元素 in 可迭代对象 if 可迭代对象元素筛选条件] ->if 条件判断非必要
# 常规方法生成10以内的偶数,并将生成的偶数扩大一百倍
lis = []
for i in range(10):
if i % 2 == 0:
i = i * 100
lis.append(i)
# 语法糖
syntactic_sugar = [i * 100 for i in range(10) if i % 2 == 0]
print(lis, syntactic_sugar)
# 输出:[0, 200, 400, 600, 800] [0, 200, 400, 600, 800]
如果觉得这样写语法糖过长可读性较差,我们可以对语法糖分行
syntactic_sugar = [i * 100
for i in range(10)
if i % 2 == 0]
优先看in之后的迭代对象是什么,之后看if后的条件,会取出那些迭代对象,最看第一行中对取出的迭代对象会做如何处理
在语法糖开头的i可以是一个判断语句,这时候列举就会输出对所以符合if条件的迭代对象的判断结果
syntactic_sugar = [i * 100 > 500 for i in range(10) if i % 2 == 0]
print(lis, syntactic_sugar) # [False, False, False, True, True]
并且列表推导式可以嵌套使用假设第一次迭代输出的元素还是可迭代对象,还可以在进行for in进行再次迭代。
使用方式推广
语法糖不仅可以用于列表,还能用于字典与集合,集合的使用方式和列表类似,只是吧[中括号]换成{大括号},不过字典使用的时候要注意,迭代字典(dict)类型时候,默认只读取为.keys()方法,如果想要读取所有内容,需要在迭代对象后加.items方法
# 使用字典的语法糖将键值调换位置
dic = {'姓名': '寻觅',
'年龄': '20',
'现状': '单身',
'需求': '女票',
'急迫程度': '高'
}
syntactic_sugar = {value: key for key, value in dic.items()}
这时候你会想[中括号]是列表的语法糖,{大括号}是字典与集合的语法糖,哪(小括号)会不会生成元组的语法糖?
如果你这样想,哪很不幸的告诉你,你想错了,如果我们将列表推广式的中括号改为小括号你会惊奇的发现,它输出的竟然是一个地址
syntactic_sugar = (i for i in range(10) if i % 2 == 0)
print(syntactic_sugar) # <generator object <genexpr> at 0x00000182C27A5F90>
我们获得的输出并不是我们想要的值而变成了一个(generator)生成器类型的地址。
生成器
生成器是迭代器的一种,可以做可控的迭代行为,生成器用多少做多少的行为可以避免资源的浪费,假设我们现在生成一个包含数百个数据的列表,但我们可能当下只会用到前几个数据,选择生成器就可以优化生成数据的占用情况。
使用方法
除了上述将列表推导式的[中括号]变为(小括号)后会获得列表推广式,我们还可以在函数中使用yield返回值函数做生成器,函数中每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
def generator(loop_max):
"""
使用斐波那契数列来展示生成器的作用1,1,2,3,5,8,12,21,34.....
:param loop_max: 斐波那契数列的最大输出值
:return:使用yield输出斐波那契数列
"""
l, x, y = 0, 0, 1
while loop_max > l:
yield y
x, y = y, x + y
l += 1
items = generator(10)
# 直接输出生成器会是一个地址,这个地址存储着生成输出内容的算法,相较于直接输出,更加节省内存,逐个输出生成器内容有三种方式。
# ".send(None)",".__next__()","next()"
print(items)
print(items.send(None))
print(items.__next__())
print(next(items))
print('可以中途执行其他命令')
print(items.send(None))
print(items.__next__())
print(next(items))
print('生成器是可迭代对象')
for i in items:
print(i)
# 将数值全部取完后在使用next会报错“StopIteration”
# print(items.__next__())
输出内容为
生成器的.send()
方法
我们如果使用.send()
方法启动生成器必须要传入None,其根本原因是第一次启动生成器不需要发送值进入生成器.send()
顾名思义是发送的意思,发送到生成器内部。
def run():
i = 1
while True:
data = yield i
print(data)
i += 1
if __name__ == '__main__':
dome = run()
print(dome.send(None))
print(dome.send('我是send方法发送的值'))
而从列表推导式变化过来的生成器,每迭代一次后都相当于执行了yield。
generator = (i for i in range(10) if i % 2 == 0)
生成器yield
结束时会抛出异常(读取到return就属于结束)
生成器中yield from
的用法
语法: yield from 可迭代对象
- 简化可迭代对象读取
yield from
可以已更加简便的方式处理可迭代对象
如:for i in range(10): yield i
可简化为yield from range(10)
- 自动处理yield异常
yield from
可以作为委托生成器,搭建一个和yield
子生成器的桥梁,搭建桥梁后最大的作用是可以处理yiled结束后产生的StopIteration
异常,但因为委托生成器同样是生成器,也会产生StopIteration
异常,所以我们可以给委托生成器一个死循环,或者for i in range(2)
每次让委托生成器都能循环一次,以防异常,或者在委托生成器中在加入一个yield,也可以防止异常
class generator:
# 生成器
def run(self):
i = 1
while True:
data = yield i
if data is None:
break
print('当前传入数据为:', data)
i += 1
return i
if __name__ == '__main__':
demo = generator().run()
# 激活生成器
print(demo.send(None))
print(demo.send(10))
print(demo.send(20))
# 关闭生成器
demo.send(None)
当没有委托生成器时,结束生成器会产生如下异常
当存在委托生成器时,生成器结束后不会抛出异常
class generator:
# 子生成器
def run(self):
i = 1
while True:
data = yield i
if data is None:
break
print('当前传入数据为:', data)
i += 1
return i
# 委托生成器
def main(self):
# 这里添加while是因为无论是使用yield还是使用yield from生成器
while True:
# for i in range(2):
data = yield from self.run()
print(f'生成器在运行{data}次后退出')
# yield
if __name__ == '__main__':
demo = generator().main()
# 激活生成器
print(demo.send(None))
print(demo.send(10))
print(demo.send(20))
# 关闭生成器
demo.send(None)
生成器与协程
多任务中,线程进程与协程各有特点,而其中Python的协程发展就是在生成器基础上的扩展和改进,甚至在Python3.7之前,生成器和协程用的都是yield这个相同的关键字,yield
相当于协程中await
的作用async
实际意义并不大,他的主要作用是声明,让协程的识别度更高,不会被人混淆。具体协程的用法可以在Python的线程、进程与协程这里查看。