操作系统会为每个函数分配一个栈帧,但是对于python中生成器所在函数,其栈帧是分配在堆上面的,所以其函数运行状态能够一值保存。此即生成器实现原理。
做个实验,打印生成器函数地址和普通函数地址
def yieldFunc():
for i in range(5):
yield i
def normalFunc1():
for i in range(4, 5):
return i
def normalFunc2():
for i in range(3):
return i
a = yieldFunc()
b = yieldFunc()
c = yieldFunc()
d = normalFunc1()
e = normalFunc2()
print(f"a in yieldFunc: {hex(id(a))}")
print(f"b in yieldFunc: {hex(id(b))}")
print(f"c in yieldFunc: {hex(id(c))}")
print(f"d in normalFunc: {hex(id(d))}")
print(f"e in normalFunc: {hex(id(e))}")
Output
a in yieldFunc: 0x159f65b5b10
b in yieldFunc: 0x159f65b5c78
c in yieldFunc: 0x159f65b5cf0
d in normalFunc: 0x7ffd891f62f0
e in normalFunc: 0x7ffd891f6270
上述实验可以论证,在yield函数中的变量是存在堆中的,普通函数的变量是存在栈中的(我的系统中堆向上增长,栈向下增长)
协程是一种基于生成器的抽象模式。协程不是并行的,协程是单线程的!
协程是一种生产者消费者模型的设计模式。
请看下面代码:
class Producer:
def produce(self):
while True:
data = generateData()
sendToConsumer(data)
class Consumer:
def consume(self):
while True:
data = receiveFromProducer()
onReceivedData(data)
Producer一直生产数据,然后发送给Consumer。Consumer一直读取Producer生产的数据,然后做相应处理。这是两个并行的过程,但是他们实际又是相互依赖的,Consumer每次只能处理一个Producer生产的数据。当Consumer处理数据时,Producer被generateData() 或者sendToConsumer() block,当Producer生产数据时,Consumer又会一直被receiveFromProducer() block。特别是有IO操作时对系统的性能影响很大。
所以更好的设计模式应是由Consumer来驱动。参见如下代码:
def sendToConsumer(data):
originalSendToConsumer(data)
yield data
class Producer:
def produce(self):
while True:
data = generateData()
sendToConsumer()
def receiveFromProducer(producer):
producer.produce()
data = originalReceiveFromProducer()
yield data
class Consumer:
def __init__(self, producer):
self.producer = producer
def consume(self):
while True:
data = receiveFromProducer(self.producer)
onReceivedData(data)
producer = Producer()
consumer = Consumer(producer)
while True:
consumer.consume()
只有当Consumer需要数据时,Producer才生产数据。
更加典型的例子就是管道: produce() | consume()。当consume()需要数据写入时,管道resume(produce())。