第17條 在參數上迭代時,要多加小心

如果函數接收的參數是個對象列表,那麼很有可能要在這個列表上進行迭代。
案例1:統計每個城市旅遊的人數,以及百分比

### 標準化函數
def normalize(number):
    total = sum(number)
    result = []
    for value in number:
        percent = value * 100 / total
        result.append(percent)
    return result
if __name__ == "__main__":
    visit = [10,20,90]
    precent = normalize(visit)
    print(precent)

如果此時城市很多,那麼會將城市的人口文件存儲在文件中,然後從該文件中讀取數據。而在讀取文件數據的操作上,可以使用前面介紹的生成器來編寫函數。

### 標準化函數
def normalize(number):
    total = sum(number)
    result = []
    for value in number:
        percent = value * 100 / total
        result.append(percent)
    return result
    
#### 生成器
def read_visits(data_path):
    with open(data_path) as f:
        for line in f:
            yield int(line)
if __name__ == "__main__":
    it = read_visits("./data/visit.txt")
    precent = normalize(it)
    print()

這種情況下,向normalize傳遞的迭代器itsum函數中已經被迭代過一次,在for中已經失效,因此程序的輸出爲[]

Solution1: 爲此我們可以將傳進去的迭代器存放到一份列表中,隨後的操作針對列表進行。

### 標準化函數
def normalize(number):
    number = list(number) ##取出數據,迭代器失效
    total = sum(number)
    result = []
    for value in number:
        percent = value * 100 / total
        result.append(percent)
    return result
#### 生成器
def read_visits(data_path):
    with open(data_path) as f:
        for line in f:
            yield int(line)
if __name__ == "__main__":
    it = read_visits("./data/visit.txt")
    precent = normalize(it)
    print()

但是這種操作,當數據量比較大時,會帶來內存消耗。

Solution2: 另外一種解決方法是,通過參數來接收另外一個函數,而這個函數每次調用返回一個新的迭代器

### 標準化函數
def normalize(get_iter):
    total = sum(get_iter()) ##New Iterator
    result = []
    for value in get_iter(): ## New Iterator
        percent = value * 100 / total
        result.append(percent)
    return result
#### 生成器
def read_visits(data_path):
    with open(data_path) as f:
        for line in f:
            yield int(line)
if __name__ == "__main__":
    path = "./data/visit.txt"
    precent = normalize(lambda:read_visits(path))
    print()

這種方法雖然可性,但是仍然沒有下面這種方法優美。

Solution3:爲了能夠使得迭代器在sum函數中和for中均獲得新的迭代器,可以新編寫一種實現迭代器協議

Python的for循環及相關表達式遍歷某種容器時,會依靠這個迭代器協議。例如:在執行for x in values這樣的語句中,Python實際上會調用iter(values).內置的iter又會調用values.__iter__方法。因此,只要把的生成器封裝成一個自定義類,同時重寫 __iter__ 方法即可。

def normalize(number):
    total = sum(number)
    result = []
    for value in number:
        percent = value * 100 / total
        result.append(percent)
    return result
class ReadVisits(object):
    def __init__(self,data_path) -> None:
        self.data_path = data_path
    def __iter__(self):
        with open(self.data_path) as f:
            for line in f:
                yield int(line)
if __name__ == "__main__":
    visit = ReadVisits(path)
    precent = normalize(visit)
    print()

normalizesum方法會調用ReadVisits.__iter__方法,從而得到新的迭代器,而接下來的for循環,也會調用__iter__方法,從而得到另外一個迭代器。
這麼做唯一的缺點在於,需要多次讀取輸入數據

進一步,在開發中爲了保證normalize接收的參數爲自定義的容器而迭代器,可以使用iter函數的返回值進行判斷,兩次調用iter函數,結果相同則爲迭代器,否則爲容器。

def normalize(number):
    if iter(number) is iter(number):
        raise TypeError('Must supply a contanier')
    total = sum(number)
    result = []
    for value in number:
        percent = value * 100 / total
        result.append(percent)
    return result
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章