操作系統會爲每個函數分配一個棧幀,但是對於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())。