运用类似的思想,定义一个 HTMLTableFormatter 类,生成具有以下输出的表格:
<tr><th>Name</th><th>Shares</th><th>Price</th><th>Change</th></tr> <tr><td>AA</td><td>100</td><td>9.22</td><td>-22.98</td></tr> <tr><td>IBM</td><td>50</td><td>106.28</td><td>15.18</td></tr> <tr><td>CAT</td><td>150</td><td>35.46</td><td>-47.98</td></tr> <tr><td>MSFT</td><td>200</td><td>20.89</td><td>-30.34</td></tr> <tr><td>GE</td><td>95</td><td>13.48</td><td>-26.89</td></tr> <tr><td>MSFT</td><td>50</td><td>20.89</td><td>-44.21</td></tr> <tr><td>IBM</td><td>100</td><td>106.28</td><td>35.84</td></tr>请通过修改主程序来测试你的代码。 主程序创建的是 HTMLTableFormatter 对象,而不是 CSVTableFormatter 对象。
练习 4.7:多态面向对象编程(oop)的一个主要特性是:可以将对象插入程序中,并且不必更改现有代码即可运行。例如,如果你编写了一个预期会使用 TableFormatter 对象的程序,那么不管你给它什么类型的 TableFormatter ,它都能正常工作。这样的行为有时被称为“多态”。
一个需要指出的潜在问题是:弄清楚如何让用户选择它们想要的格式。像 TextTableFormatter 一样直接使用类名通常有点烦人。因此,你应该考虑一些简化的方法。如:你可以在代码中嵌入 if 语句:
def portfolio_report(portfoliofile, pricefile, fmt='txt'): ''' Make a stock report given portfolio and price data files. ''' # Read data files portfolio = read_portfolio(portfoliofile) prices = read_prices(pricefile) # Create the report data report = make_report_data(portfolio, prices) # Print it out if fmt == 'txt': formatter = tableformat.TextTableFormatter() elif fmt == 'csv': formatter = tableformat.CSVTableFormatter() elif fmt == 'html': formatter = tableformat.HTMLTableFormatter() else: raise RuntimeError(f'Unknown format {fmt}') print_report(report, formatter)虽然在此代码中,用户可以指定一个简化的名称(如'txt' 或 'csv')来选择格式,但是,像这样在 portfolio_report() 函数中使用大量的 if 语句真的是最好的思想吗?把这些代码移入其它通用函数中可能更好。
在 tableformat.py 文件中,请添加一个名为 create_formatter(name) 的函数,该函数允许用户创建给定输出名(如'txt','csv',或 'html')的格式器(formatter)。请像下面这样修改 portfolio_report() 函数:
def portfolio_report(portfoliofile, pricefile, fmt='txt'): ''' Make a stock report given portfolio and price data files. ''' # Read data files portfolio = read_portfolio(portfoliofile) prices = read_prices(pricefile) # Create the report data report = make_report_data(portfolio, prices) # Print it out formatter = tableformat.create_formatter(fmt) print_report(report, formatter)尝试使用不同的格式调用该函数,确保它能够正常工作。
练习 4.8:汇总请修改 report.py 程序,以便 portfolio_report() 函数使用可选参数指定输出格式。示例:
>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv', 'txt') Name Shares Price Change ---------- ---------- ---------- ---------- AA 100 9.22 -22.98 IBM 50 106.28 15.18 CAT 150 35.46 -47.98 MSFT 200 20.89 -30.34 GE 95 13.48 -26.89 MSFT 50 20.89 -44.21 IBM 100 106.28 35.84 >>>请修改主程序,以便可以在命令行上指定输出格式:
bash $ python3 report.py Data/portfolio.csv Data/prices.csv csv Name,Shares,Price,Change AA,100,9.22,-22.98 IBM,50,106.28,15.18 CAT,150,35.46,-47.98 MSFT,200,20.89,-30.34 GE,95,13.48,-26.89 MSFT,50,20.89,-44.21 IBM,100,106.28,35.84 bash $ 讨论在库和框架中,编写可扩展程序是继承的最常见用途之一。例如,框架指导你定义一个自己的对象,该对象继承自已提供的基类。然后你可以添加实现各种功能的函数。
另一个更深层次的概念是“拥有抽象的思想”。在练习中,我们定义了自己的类,用于格式化表格。你可能会看一下自己的代码,然后告诉自己“我应该只使用格式化库或其它人已经编写的东西!”。不,你应该同时使用自己的类和库。使用自己的类可以降低程序的耦合性,增加程序的灵活性。只要你的程序使用的应用接口来自于自己定义的类,那么,只要你想,你就可以更改程序的内部实现以使其按照你想的那样工作。你可以编写全定制(all-custom)代码,也可以使用第三方包(package)。当发现更好的包时,你可以将一个第三方包替换为另一个包。这并不重要——只要你保留这个接口,应用程序代码都不会中断。这是一种强大的思想,这也是为什么应该使用继承的原因之一。