目录 | 上一节 (5.2 封装) | [下一节 (6.2 自定义迭代)]()

6.1 迭代协定

本节将探索迭代的底层过程。

迭代无处不在

许多对象都反对迭代:

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 语句:

for x in obj:    # statements

for 语句的背地产生了什么?

_iter = obj.__iter__()        # Get iterator objectwhile True:    try:        x = _iter.__next__()  # Get next item        # statements ...    except StopIteration:     # No more items        break

所有可利用于 for-loop 的对象都实现了上述底层迭代协定。

示例:手动迭代一个列表。

>>> 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__() 办法获取下一个元素。

>>> 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.pyclass 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 fileparsefrom stock import Stockfrom portfolio import Portfoliodef 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.pyimport reportdef 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 portfolioTrue>>> 'AAPL' in portfolioFalse>>>

无关上述代码的一个重要发现——通常,如果一段代码和 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