使用迭代器接收資料並自動停止

firefule發表於2021-09-11

使用迭代器接收資料並自動停止

假設有一個 Redis 集合,裡面有 N 條資料,你不停從裡面lpop資料,直到某一條資料的值為'Stop'字串為止(已知裡面必有一條資料為'Stop'字串,但其位置不知道)。

這個需求看起來很簡單,於是你立刻就著手寫出了程式碼:

import redis

client = redis.Redis()

def read_data():
    datas = []
    while True:
        data = client.lpop().decode()
        if data == 'Stop':
            break
        datas.append(data)
    return datas

現在問題來了,如果 Redis 裡面的資料非常多,已經超過了你的記憶體容量怎麼辦?資料全部放在datas列表裡面再返回顯然是不可取的做法。

好在,這些資料讀取出來以後,會傳給一個parse函式,並且這個函式是一條一條處理資料的,它處理完成以後,就可以把資料丟棄了。

於是你可能會這樣改寫程式碼:

import redis

client = redis.Redis()

def read_data():
    while True:
        data = client.lpop().decode()
        if data == 'Stop':
            break
        parse(data)

但我們知道,在編碼規範和軟體工程裡面,建議一個函式,它應該只做一件事情,而現在read_data()函式卻做了兩件事情:1. 從 Redis 裡面讀取資料。2.呼叫parse()函式。

那麼我們有沒有辦法把他們區分開來呢?如何讓read_data能返回資料,但是又不會把記憶體撐爆呢?

這個時候,我們就可以使用生成器來解決問題:

import redis

client = redis.Redis()

def read_data():
    while True:
        data = client.lpop().decode()
        if data == 'Stop':
            break
        yield data

def parse_data():
    for data in read_data():
        parse(data)

在這個程式碼裡面,read_data變成了生成器函式,它返回一個生成器,對生成器進行迭代的時候,每次返回一條資料,這一條資料立即傳給parse()函式。整個過程源源不斷,生生不息。不需要額外建立一個列表用來存放資料。

那麼程式碼還能不能繼續簡化呢?此時我們就可以使用iter關鍵字了。

使用了iter關鍵字的效果如下圖所示:

import redis

client = redis.Redis()

def read_data():
    data = client.lpop().decode()
    return data

def parse_data():
    for data in iter(read_data, 'Stop'):
        parse(data)

其中,read_data現在每執行一次只會返回列表最左邊的資料。但是當我們直接使用iter(read_data, 'Stop')的時候,就會得到一個迭代器。對這個迭代器進行迭代,相當於在While True裡面不停執行read_data函式,直到某一次迭代的時候,read_data函式返回了Stop,就停止。

當然如果你想炫技的話,還可以進一步簡化:

import redis

client = redis.Redis()

def parse_data():
    for data in iter(lambda: client.lpop().decode(), 'Stop'):
        parse(data)

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1834/viewspace-2835902/,如需轉載,請註明出處,否則將追究法律責任。

相關文章