如果函數接收的參數是個對象列表,那麼很有可能要在這個列表上進行迭代。
案例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
傳遞的迭代器it
在sum
函數中已經被迭代過一次,在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()
normalize
的sum
方法會調用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