翻譯:《實用的Python編程》06_01_Iteration_protocol

目錄 | 上一節 (5.2 封裝) | 下一節 (6.2 自定義迭代)html

6.1 迭代協議

本節將探究迭代的底層過程。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:
    ...

練習

練習 6.1:迭代演示

建立如下列表:翻譯

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),直到文件末尾。觀察會發生什麼。

練習 6.2:支持迭代

有時候,你可能想要使本身的類對象支持迭代——尤爲是你的類對象封裝了已有的列表或者其它可迭代對象時。請在新的 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
>>>

練習 6.3:建立一個更合適的容器

一般,咱們建立一個容器類時,不只但願該類可以迭代,同時也但願該類可以具備一些其它用途。請修改 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」 的。同理,對於容器對象,其重要組成部分應該包括:支持迭代、能夠進行索引、對所包含的元素進行判斷,以及其它操做等等。

目錄 | 上一節 (5.2 封裝) | 下一節 (6.2 自定義迭代)

注:完整翻譯見 https://github.com/codists/practical-python-zh

相關文章
相關標籤/搜索