python 协程
python 生成器的作用
协程在多个模型流式输出中的使用实例
1. 生成器
参考:
https://www.cainiaojc.com/python/python-generator.html
1.1. 什么是生成器?
在 python 中,一边循环一边计算的机制,称为生成器:generator.
1.2. 生成器有什么优点?
1、节约内存。python在使用生成器时对延迟操作提供了支持。所谓延迟,是指在需要的时候才产生结果,而不是立即产生结果。这样在需要的时候才去调用结果,而不是将结果提前存储起来要节约内存。比如用列表的形式存放较大数据将会占用不少内存。这是生成器的主要好处。比如大数据中,使用生成器来调取数据结果而不是列表来处理数据,因为这样可以节约内存。
2、迭代到下一次的调用时,所使用的参数都是第一次所保留下的。
1.3. 在python中创建生成器
在python中,有两种创建生成器的方式:
1.3.1. 生成器表达式
类似与列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表。
使用列表推导,将会一次返回所有结果:
li=[i**2 for i in range(5)]
print(li)
运行结果:
[0, 1, 4, 9, 16]
将列表推导的中括号,替换成圆括号,就是一个生成器表达式:
li=(i**2 for i in range(5))
print(li)
for x in range(5):
print(next(li))
运行结果:
<generator object <genexpr> at 0x7f124b6c8120>
0
1
4
9
16
注意:创建完成生成器后,可以使用next()来调用生成器的数据结果,每调用一次返回一个值,直到调用结束。调用结束后li中为空的,不在有任何值。要想使用它,只能重新创建新的生成器。(生成器表达式的第四行代码也可以改成:print(x).)
1.3.2. 生成器函数
常规函数定义,但是使用 yield 语句而不是 return 语句返回结果。yield 语句每次返回一个结果,但每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。我们下面来看一个例子。下面为一个可以无穷生产奇数的生成器函数。
def odd():
n = 1
while True:
yield n
n += 2
odd_num = odd()
count = 0
for o in odd_num:
if count >= 5:
break
print(o)
count += 1
运行结果
1
3
5
7
9
上面的函数中,yield 是必备的,当一个普通函数中包含 yield 时,系统会默认为是一个 generator。
1.3.3. 生成器的两种方法
next() 和 send()
其中,next() 方法和 next 的作用是一样的。如下所示。
def fib(times):
i=0 # 判断退出使用
n=1 # 计算奇数使用
while i < times:
yield n
n+=2
i+=1
f = fib(5)
for index in range(5):
# print(next(f))
print(f.__next__())
运行结果:
1
3
5
7
9
从上面的程序中可以看出,f.next() 和 next(f) 是作用是一样的。
下面再来看看send() 方法的使用。
def fib(times):
i=0 # 判断退出使用
n=1 # 计算奇数使用
while i < times:
# 添加变量 temp
temp = yield n
print("fib", temp)
n+=2
i+=1
f = fib(5)
print(f.__next__())
print(f.send("haha"))
print(f.send("heihei"))
运行结果:
1
fib haha
3
fib heihei
5
从上面代码可以看出:使用 send() 时,必须在 yield 前面加上一个变量,并打印这个变量。在调用 send() 方法时其前面需要至少使用一次next() 或 next() 方法,因为生成器不可以在使用之前导入任何非 None 参数。由此可以知道,send() 是用来向生成器中导入参数并返回该参数的,与该参数一起返回的还有生成器中原先保存的数据。
1.3.4. 再看生成器
前面已经对生成器有了感性的认识,我们以生成器函数为例,再来深入探讨一下 python 的生成器:
1、语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用 def 语句进行定义,差别在于,生成器使用 yield 语句返回一个值,而常规函数使用 return 语句返回一个值。
2、自动实现迭代器协议:对于生成器,python 会自动实现迭代器协议,以便应用到迭代背景中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用它的 next() 方法,并且,在没有值可以返回的时候,生成器自动产生 StopIteration 异常
3、状态挂起:生成器使用 yield 语句返回一个值。yield 语句挂起该生成器函数的状态,保留足够的信息,以便以后从它离开的地方继续执行。
生成器的唯一注意事项就是:生成器只能遍历一次
2. 迭代器
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历位置的对象。迭代器只能往前不能后退。
1、什么是可迭代对象
集合数据类型,如 list 、tuple、dict、set、str 等
生成器和带 yield 的 generator function
2、如何判断对象可迭代?
from collections import Iterable
isinstance([ ],Iterable)
try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable
#from collections import Iterable
print(isinstance([], Iterable))
print(isinstance("abcdef", Iterable))
print(isinstance((x for x in range(5)), Iterable))
print(isinstance(123456, Iterable))
print(isinstance(True, Iterable))
运行结果:
True
True
True
False
False
如上,命令行模式下,先导入Iterable模块,然后输入isinstance([ ],Iterable),括号中前面为待判断的对象,结果以布尔类型结束(True或False),列表是可迭代对象,因此返回True。注意:整数是不可迭代对象。
故障排除:
ImportError: cannot import name ‘Iterable’ from ‘collections’ (/home/lianap/anaconda3/lib/python3.10/collections/init.py)
参考:
https://blog.csdn.net/weixin_55201180/article/details/131345280
3、什么是迭代器呢?
**迭代器是可以被 next() 函数调用并不断返回下一个值的对象称为迭代器。**因此生成器是迭代器的子类,但是注意集合类型的可迭代对象不是迭代器。
iter() 函数:将可迭代对象转换成迭代器。
print(isinstance(iter([1, 2, 3, 4]), Iterable))
print(isinstance([1, 2, 3, 4], Iterable))
运行结果:
True
True
以上描述持怀疑态度。
3. 闭包
3.1. 什么是闭包
内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。
**闭包三要素:**嵌套函数,变量的引用,返回内部函数
# 定义一个嵌套函数(要素1)
def test(num):
def test_in(num_in):
# 内部函数引用外部函数的变量(非全局变量)(要素2)
print("sum=%s"%(num + num_in))
# 返回的结果可以被打印出来
return num,num_in
# 返回内部的函数(要素3)
return test_in
# 这里的rtn就是 test_in
rtn = test(10)
print(rtn)
# 内部函数test_in传参
print(rtn(20))
运行结果:
<function test.<locals>.test_in at 0x7fbf353c9900>
sum=30
(10, 20)
3.2. 闭包的应用
#求一元一次方程的值,输入x,求y
def fun(a,b): #其中a,b是固定的
def fun_in(x):
return a*x+b
return fun_in
f = fun(3,5)
print(f(1)) #每次输入不同的x值即可求出对应的y值
print(f(3))
运行结果:
8
14
上面的函数中,利用闭包来求一元一次方程的值,更方便,直接输入x的值即可求出对应的y的值。因为这利用了闭包可以记住外部函数的参数的特性。
4. 装饰器
4.1. 什么是装饰器(decorator)
装饰器其实就是一个闭包,把一个函数当作参数然后返回一个替代版函数。
装饰器有2个特性:
1、可以把被装饰的函数替换成其他函数
2、可以在加载模块时候立即执行
举例如下:
A、装饰器对无参数函数进行装饰:
# 装饰器本质就是一个闭包
# 将函数当做参数传进去
def deco(fun):
def inner():
print("你想进行的操作1")
fun()
print("你想进行的操作2")
return inner
# @deco 是一个“语法糖”
@deco
def test():
print("test函数的操作")
# 这个test已经被装饰了,不是原来的test
# 等同于
# rtn = deco(test)
# rtn()
test()
运行结果:
你想进行的操作1
test函数的操作
你想进行的操作2
4.2. 装饰器的功能有
1、引入日志
2、函数执行时间统计
3、执行函数前预备处理
4、执行函数后清理功能
5、权限校验等场景
6、缓存
4.3. 装饰器的分类
装饰器可分为:对有无参数函数进行装饰的装饰器,对有无返回值函数进行装饰的装饰器,组合起来一共有4种。
即:装饰器对无参数无返回值的函数进行装饰,装饰器对无参数有返回值的函数进行装饰,装饰器对有参数无返回值的函数进行装饰,装饰器对有参数有返回值的函数进行装饰。
4.3.1. 装饰器对无参数函数进行装饰
#定义函数:完成包裹数据
def makeBold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
#定义函数:完成包裹数据
def makeItalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makeBold
def test1():
return "hello world - 1"
@makeItalic
def test2():
return "hello world - 2"
@makeBold
@makeItalic
def test3():
return "hello world - 3"
print(test1())
print(test2())
print(test3())
运行结果:
<b>hello world - 1</b>
<i>hello world - 2</i>
<b><i>hello world - 3</i></b>
上面这个例子是做网页的时候对字体进行设置,对 test1() 进行加粗,对 test2() 斜体处理,对 test3() 先斜体在加粗。
注意:对一个函数可以同时使用多个装饰器,装饰顺序由内而外。
4.3.2. 装饰器对有参数函数进行装饰
# 定义一个装饰器
def deco(func):
def inner(a, b): # 内部函数的参数必须和被装饰的函数保持一致
print("添加的功能1")
func(a, b)
print("添加的功能2")
return inner
@deco
# 有参数的函数
def sum(a, b):
print(a+b)
sum(10,20)
运行结果:
添加的功能1
30
添加的功能2
当装饰器装饰有参数的函数时,装饰器内部的函数也必须带有和其相同的参数,因为被装饰的参数会被当成参数传进装饰器的内部函数中,如果两者的参数不一致,会报错。
4.3.3. 装饰器对不定长参数函数进行装饰
from time import ctime,sleep
#定义一个装饰器,装饰不定长参数函数
def deco(func):
def inner(*args, **kwargs):
print("%s called at the %s"%(func.__name__,ctime()))
func(*args,**kwargs)
return inner
@deco
def test1(a, b, c):
print(a+b+c)
@deco
def test2(a, b):
print(a+b)
test1(10,20,30)
sleep(2)
test1(30,40,50)
sleep(1)
test2(10,20)
运行结果:
test1 called at the Tue Nov 28 22:23:36 2023
60
test1 called at the Tue Nov 28 22:23:38 2023
120
test2 called at the Tue Nov 28 22:23:39 2023
30
4.3.4. 装饰器对有返回值的函数进行装饰
from time import ctime,sleep
#定义一个装饰器,装饰不定长参数函数
def deco(func):
def inner(*args,**kwargs):
print("%s called at the %s"%(func.__name__,ctime()))
return func(*args,**kwargs)
return inner
#上面的装饰器是一个万能的装饰器,因为它可以装饰任意一种函数
#包括有无参数函数和有无返回值函数
@deco
def test():
return "---ha--ha---"
t = test()
print(t)
运行结果:
test called at the Tue Nov 28 22:26:46 2023
---ha--ha---
该装饰器是一个万能的装饰器,因为其可以用来装饰任何函数,包括有无参数函数和有无返回值函数。
参考:
https://blog.51cto.com/u_39029/6405726