Python知识点
什么是yield
yield的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作。
def foo():
print("开始...")
while True:
response = yield 1
print("response = ", response)
def main():
g = foo()
print("第一次yield返回值:", next(g))
print("第二次next")
print("第二次yield返回值:", next(g))
输出:
开始...
第一次yield返回值: 1
第二次next
response = None
第二次yield返回值: 1
- foo函数中包含yield关键字,所以foo函数不会真正执行,而是得到一个生成器g
- 对生成g调用next方法时,foo函数才会真正执行,先执行foo函数中的print(“开始…"),然后进入while循环
- 执行遇到yield关键字,先把yield看成是return,return 1之后,程序停止。并没有完成赋值给response的操作,到这里next(g)执行完成
- 执行print(“第二次next”)
- 执行新的next(g),执行的位置要从上一个next方法停止的位置开始,即要完成赋值操作给response,因为上一个next已经return了,所以 右边相当于没有值,输出就是response = None
- 还没遇到第二次的yield,所以程序还未停止,进入while循环,遇到yield 1,返回打印1
使用send的例子
def foo():
print("开始...")
while True:
response = yield 1
print("response = ", response)
def main():
g = foo()
print("第一次yield返回值:", next(g))
print("第二次next")
print("第二次yield返回值:", g.send(2))
输出:
开始...
第一次yield返回值: 1
第二次next
response = 2
第二次yield返回值: 1
前面执行过程跟上面那个例子一样,从g.send(2)开始,程序会从上一个next()停止的下一步操作开始,send的话是会把2赋值给response变量。 其实next()和send()在一定意义上作用是相似的,区别是send()可以传递yield表达式的值进去,而next()不能传递特定的值,只能传递None进去。因此next(c)可以等价于c.send(None)。
生产者-消费者
yield/send实现
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('消费者: 消费 %s...' % n)
r = '200 OK'
def producer(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('生产者: 生产 %s...' % n)
r = c.send(n)
print('生产者: 从消费里那里返回: %s' % r)
c.close()
def main():
c = consumer()
producer(c)
if __name__ == "__main__":
main()
- consumer函数定义了一个生成器,它能够消费由生产者发送的值。
- 在consumer函数中,while True 创建了一个无限循环,在这个循环中,生成器始终等待来自生产者的值。
- n = yield r 这一行既是yield表达式的输出,也是输入的接收点。该生成器一开始会返回空字符串 r='',之后每次循环会返回 ‘200 OK’ 给生产者,并且等待下一个来自生产者的send。
- 如果生产者发送了None或没有发送值(即 n 为假),消费者将会直接返回,生成器结束。
生产者的producer函数做了以下几件事情:
- 使用c.send(None) 启动或"预激活” 消费者生成器,这是开始发送值给生成器之前的必要步骤。
- produce 开始在一个循环中生产值,从1到5。
- 对于每个新的整数 n,生产者通过调用 c.send(n) 将其发送到消费者,同时接收从消费者返回的值,并将其打印出来。
- 生产者在发送了5个值后结束循环,并通过 c.close() 关闭消费者生成器。
main函数会先创建消费者生成器,然后启动生产者函数。最终运行脚本时,会依次打印出生产者生产的值和消费者消费的值,以及消费者返回的结果(‘200 OK’)。运行过程中,生产者和消费者通过send和yield操作进行交互,协同工作完成生产消费任务。
输出:
生产者: 生产 1...
消费者: 消费 1...
生产者: 从消费里那里返回: 200 OK
生产者: 生产 2...
消费者: 消费 2...
生产者: 从消费里那里返回: 200 OK
生产者: 生产 3...
消费者: 消费 3...
生产者: 从消费里那里返回: 200 OK
生产者: 生产 4...
消费者: 消费 4...
生产者: 从消费里那里返回: 200 OK
生产者: 生产 5...
消费者: 消费 5...
生产者: 从消费里那里返回: 200 OK
async/await协程实现
import asyncio
import random
import time
async def consumer(queue, id):
while True:
val = await queue.get()
if val is None: # 如果接收到None,则认为是结束信号
# 通知队列任务完成
queue.task_done()
break
print('{} get a val: {}'.format(id, val))
# 模拟一秒钟处理时间
await asyncio.sleep(1)
# 通知队列任务完成
queue.task_done()
async def producer(queue, id):
for _ in range(5):
val = random.randint(1, 10)
await queue.put(val)
print('{} put a val: {}'.format(id, val))
await asyncio.sleep(1)
async def main():
queue = asyncio.Queue()
consumers = [asyncio.create_task(
consumer(queue, f'consumer_{i+1}')) for i in range(2)]
producers = [asyncio.create_task(
producer(queue, f'producer_{i+1}')) for i in range(2)]
# 等待所有生产者结束
await asyncio.gather(*producers)
# 发送结束信号给消费者,有几个消费者就发送几个None
for _ in consumers:
await queue.put(None)
# 等待所有任务被消费
await queue.join() # 这确保了队列中的所有任务被处理
# 取消所有消费者
for c in consumers:
c.cancel()
# 等待所有消费者完成取消
await asyncio.gather(*consumers, return_exceptions=True)
if __name__ == '__main__':
start_time = time.time()
asyncio.run(main())
print('time cost:', time.time() - start_time)
输出:
producer_1 put a val: 7
producer_2 put a val: 5
consumer_1 get a val: 7
consumer_2 get a val: 5
producer_1 put a val: 9
producer_2 put a val: 2
consumer_1 get a val: 9
consumer_2 get a val: 2
producer_1 put a val: 9
producer_2 put a val: 3
consumer_1 get a val: 9
consumer_2 get a val: 3
producer_1 put a val: 5
producer_2 put a val: 1
consumer_1 get a val: 5
consumer_2 get a val: 1
producer_1 put a val: 6
producer_2 put a val: 7
consumer_1 get a val: 6
consumer_2 get a val: 7
time cost: 5.012088060379028
隐式new方法和init方法
- __new__()方法用来创建实例,它是class的方法,是个静态方法,执行完了需要返回创建的类的实例。
- __init__()方法用来初始化实例,在实例对象被创建后被调用,是实例对象的方法,通常用于设置实例对象的初始属性。__init__()方法将不返回任何信息。
- 类中同时出现了__init__()方法和__new__()方法,调用顺序为:先调用__new__()方法,后调用__init__()方法。__new__()方法如果报错,则不会调用__init__()方法。
重写__new__方法
def __new__(cls):
return super().__new__(cls) # 或return object.__new__(cls)
隐式new方法应用 - 单例模式
class TestCls:
_xxx = None
def __new__(cls):
print('__new__ func called')
if cls._xxx == None:
cls._xxx = object.__new__(cls)
return cls._xxx
else:
return cls._xxx
def __init__(self):
print('__init__ func called')
t1 = TestCls()
print(t1)
t2 = TestCls()
print(t2)
输出:
__new__ func called
__init__ func called
<__main__.TestCls object at 0x7f8fe75befe0>
__new__ func called
__init__ func called
<__main__.TestCls object at 0x7f8fe75befe0>
可以看出是同个对象,而且隐式new函数先于隐式init函数被调用
functools.wraps
保证被装饰器装饰后的函数还拥有原来的属性,wraps通过partial以及update_wrapper来实现。比如保留原有函数名和doc
没用wraps的装饰器
def decorate(func):
def wrapper():
return '<decorate>' + func() + '</decorate>'
return wrapper
@decorate
def to_decorate():
return 'to decorate!'
def not_to_decorate():
return 'not to decorate'
print(to_decorate)
print(not_to_decorate)
输出:
<function decorate.<locals>.wrapper at 0x7fa1e6ddec20>
<function not_to_decorate at 0x7fa1e6ddfd90>
从输出来看,装饰后的函数名发生了变化
用了wraps的装饰器
import functools
def decorate(func):
@functools.wraps(func)
def wrapper():
return '<decorate>' + func() + '</decorate>'
return wrapper
@decorate
def to_decorate():
return 'to decorate!'
def not_to_decorate():
return 'not to decorate'
print(to_decorate)
print(not_to_decorate)
输出:
<function to_decorate at 0x7fd9bfdd2c20>
<function not_to_decorate at 0x7fd9bfdd3d90>
从输出来看,装饰后的函数跟原来的函数保持了一致。知乎上有篇详解functools.wraps原理的文章:https://zhuanlan.zhihu.com/p/45535784
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付