目錄 | 上一節 (5.2 封裝) | 下一節 (6.2 自定義迭代)html
本節將探究迭代的底層過程。python
許多對象都支持迭代:git
a = 'hello' for c in a: # Loop over characters in a ... b = { 'name': 'Dave', 'password':'foo'} for k in b: # Loop over keys in dictionary ... c = [1,2,3,4] for i in c: # Loop over items in a list/tuple ... f = open('foo.txt') for x in f: # Loop over lines in a file ...
考慮如下 for
語句:github
for x in obj: # statements
for
語句的背後發生了什麼?app
_iter = obj.__iter__() # Get iterator object while True: try: x = _iter.__next__() # Get next item # statements ... except StopIteration: # No more items break
全部可應用於 for-loop
的對象都實現了上述底層迭代協議。函數
示例:手動迭代一個列表。oop
>>> x = [1,2,3] >>> it = x.__iter__() >>> it <listiterator object at 0x590b0> >>> it.__next__() 1 >>> it.__next__() 2 >>> it.__next__() 3 >>> it.__next__() Traceback (most recent call last): File "<stdin>", line 1, in ? StopIteration >>>
若是想要將迭代添加到本身的對象中,那麼瞭解迭代很是有用。例如:自定義容器。測試
class Portfolio: def __init__(self): self.holdings = [] def __iter__(self): return self.holdings.__iter__() ... port = Portfolio() for s in port: ...
建立如下列表:翻譯
a = [1,9,4,25,16]
請手動迭代該列表:先調用 __iter__()
方法獲取一個迭代器,而後調用 __next__()
方法獲取下一個元素。code
>>> i = a.__iter__() >>> i <listiterator object at 0x64c10> >>> i.__next__() 1 >>> i.__next__() 9 >>> i.__next__() 4 >>> i.__next__() 25 >>> i.__next__() 16 >>> i.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>>
內置函數 next()
是調用迭代器的 __next__()
方法的快捷方式。嘗試在一個文件對象上使用 next()
方法:
>>> f = open('Data/portfolio.csv') >>> f.__iter__() # Note: This returns the file itself <_io.TextIOWrapper name='Data/portfolio.csv' mode='r' encoding='UTF-8'> >>> next(f) 'name,shares,price\n' >>> next(f) '"AA",100,32.20\n' >>> next(f) '"IBM",50,91.10\n' >>>
持續調用 next(f)
,直到文件末尾。觀察會發生什麼。
有時候,你可能想要使本身的類對象支持迭代——尤爲是你的類對象封裝了已有的列表或者其它可迭代對象時。請在新的 portfolio.py
文件中定義以下類:
# portfolio.py class Portfolio: def __init__(self, holdings): self._holdings = holdings @property def total_cost(self): return sum([s.cost for s in self._holdings]) def tabulate_shares(self): from collections import Counter total_shares = Counter() for s in self._holdings: total_shares[s.name] += s.shares return total_shares
Portfolio 類封裝了一個列表,同時擁有一些方法,如: total_cost
property。請修改 report.py
文件中的 read_portfolio()
函數,以便 read_portfolio()
函數可以像下面這樣建立 Portfolio
類的實例:
# report.py ... import fileparse from stock import Stock from portfolio import Portfolio def read_portfolio(filename): ''' Read a stock portfolio file into a list of dictionaries with keys name, shares, and price. ''' with open(filename) as file: portdicts = fileparse.parse_csv(file, select=['name','shares','price'], types=[str,int,float]) portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ] return Portfolio(portfolio) ...
接着運行 report.py
程序。你會發現程序運行失敗,緣由很明顯,由於 Portfolio
的實例不是可迭代對象。
>>> import report >>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv') ... crashes ...
能夠經過修改 Portfolio
類,使 Portfolio
類支持迭代來解決此問題:
class Portfolio: def __init__(self, holdings): self._holdings = holdings def __iter__(self): return self._holdings.__iter__() @property def total_cost(self): return sum([s.shares*s.price for s in self._holdings]) def tabulate_shares(self): from collections import Counter total_shares = Counter() for s in self._holdings: total_shares[s.name] += s.shares return total_shares
修改完成後, report.py
程序應該可以再次正常運行。同時,請修改 pcost.py
程序,以便可以像下面這樣使用新的 Portfolio
對象:
# pcost.py import report def portfolio_cost(filename): ''' Computes the total cost (shares*price) of a portfolio file ''' portfolio = report.read_portfolio(filename) return portfolio.total_cost ...
對 pcost.py
程序進行測試並確保其能正常工做:
>>> import pcost >>> pcost.portfolio_cost('Data/portfolio.csv') 44671.15 >>>
一般,咱們建立一個容器類時,不只但願該類可以迭代,同時也但願該類可以具備一些其它用途。請修改 Portfolio
類,使其具備如下這些特殊方法:
class Portfolio: def __init__(self, holdings): self._holdings = holdings def __iter__(self): return self._holdings.__iter__() def __len__(self): return len(self._holdings) def __getitem__(self, index): return self._holdings[index] def __contains__(self, name): return any([s.name == name for s in self._holdings]) @property def total_cost(self): return sum([s.shares*s.price for s in self._holdings]) def tabulate_shares(self): from collections import Counter total_shares = Counter() for s in self._holdings: total_shares[s.name] += s.shares return total_shares
如今,使用 Portfolio
類進行一些實驗:
>>> import report >>> portfolio = report.read_portfolio('Data/portfolio.csv') >>> len(portfolio) 7 >>> portfolio[0] Stock('AA', 100, 32.2) >>> portfolio[1] Stock('IBM', 50, 91.1) >>> portfolio[0:3] [Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44)] >>> 'IBM' in portfolio True >>> 'AAPL' in portfolio False >>>
有關上述代碼的一個重要發現——一般,若是一段代碼和 Python 的其它代碼"相似(speaks the common vocabulary of how other parts of Python normally work)",那麼該代碼被認爲是 「Pythonic」 的。同理,對於容器對象,其重要組成部分應該包括:支持迭代、能夠進行索引、對所包含的元素進行判斷,以及其它操做等等。