198 lines
6.9 KiB
Markdown
198 lines
6.9 KiB
Markdown
\[ [Index](index.md) | [Exercise 3.4](ex3_4.md) | [Exercise 3.6](ex3_6.md) \]
|
|
|
|
# Exercise 3.5
|
|
|
|
*Objectives:*
|
|
|
|
- Learn how to use inheritance to write extensible code.
|
|
- See a practical use of inheritance by writing a program that must
|
|
output data in a variety of user-selectable formats such as plain-text,
|
|
CSV, and HTML.
|
|
|
|
*Files Modified:* `tableformat.py`
|
|
|
|
One major use of classes in Python is in writing code that be
|
|
extended/adapted in various ways. To illustrate, in
|
|
link:ex3_2.html[Exercise 3.2] you created a function `print_table()`
|
|
that made tables. You used this to make output from the `portfolio`
|
|
list. For example:
|
|
|
|
```python
|
|
>>> import stock
|
|
>>> import reader
|
|
>>> import tableformat
|
|
>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)
|
|
>>> tableformat.print_table(portfolio, ['name','shares','price'])
|
|
name shares price
|
|
---------- ---------- ----------
|
|
AA 100 32.2
|
|
IBM 50 91.1
|
|
CAT 150 83.44
|
|
MSFT 200 51.23
|
|
GE 95 40.37
|
|
MSFT 50 65.1
|
|
IBM 100 70.44
|
|
>>>
|
|
```
|
|
|
|
Suppose you wanted the `print_table()` function to be able to
|
|
make tables in any number of output formats such as CSV, XML, HTML,
|
|
Excel, etc. Trying to modify the function to support all of those
|
|
output formats at once would be painful. A better way to do this
|
|
involves moving the output-related formatting code to a class and using
|
|
inheritance to implement different output formats.
|
|
|
|
## (a) Defining a generic formatter class
|
|
|
|
Add the following class definition to the `tableformat.py` file:
|
|
|
|
```python
|
|
class TableFormatter:
|
|
def headings(self, headers):
|
|
raise NotImplementedError()
|
|
|
|
def row(self, rowdata):
|
|
raise NotImplementedError()
|
|
```
|
|
|
|
Now, modify the `print_table()` function so that it accepts a `TableFormatter` instance
|
|
and invokes methods on it to produce output:
|
|
|
|
```python
|
|
def print_table(records, fields, formatter):
|
|
formatter.headings(fields)
|
|
for r in records:
|
|
rowdata = [getattr(r, fieldname) for fieldname in fields]
|
|
formatter.row(rowdata)
|
|
```
|
|
|
|
These two classes are meant to be used together. For example:
|
|
|
|
```
|
|
>>> import stock, reader, tableformat
|
|
>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)
|
|
>>> formatter = tableformat.TableFormatter()
|
|
>>> tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)
|
|
Traceback (most recent call last):
|
|
...
|
|
NotImplementedError
|
|
>>>
|
|
```
|
|
|
|
For now, it doesn't do much of anything interesting. You'll fix this in the next section.
|
|
|
|
## (b) Implementing a concrete formatter
|
|
|
|
The `TableFormatter` isn't meant to be used by itself. Instead, it is merely a base
|
|
for other classes that will implement the formatting. Add the following class to
|
|
`tableformat.py`:
|
|
|
|
```python
|
|
class TextTableFormatter(TableFormatter):
|
|
def headings(self, headers):
|
|
print(' '.join('%10s' % h for h in headers))
|
|
print(('-'*10 + ' ')*len(headers))
|
|
|
|
def row(self, rowdata):
|
|
print(' '.join('%10s' % d for d in rowdata))
|
|
```
|
|
|
|
Now, use your new class as follows:
|
|
|
|
```python
|
|
>>> import stock, reader, tableformat
|
|
>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)
|
|
>>> formatter = tableformat.TextTableFormatter()
|
|
>>> tableformat.print_table(portfolio, ['name','shares','price'], formatter)
|
|
name shares price
|
|
---------- ---------- ----------
|
|
AA 100 32.2
|
|
IBM 50 91.1
|
|
CAT 150 83.44
|
|
MSFT 200 51.23
|
|
GE 95 40.37
|
|
MSFT 50 65.1
|
|
IBM 100 70.44
|
|
>>>
|
|
```
|
|
|
|
## (c) Adding More Implementations
|
|
|
|
Create a class `CSVTableFormatter` that allows output to be generated in CSV format:
|
|
|
|
```python
|
|
>>> import stock, reader, tableformat
|
|
>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)
|
|
>>> formatter = tableformat.CSVTableFormatter()
|
|
>>> tableformat.print_table(portfolio, ['name','shares','price'], formatter)
|
|
name,shares,price
|
|
AA,100,32.2
|
|
IBM,50,91.1
|
|
CAT,150,83.44
|
|
MSFT,200,51.23
|
|
GE,95,40.37
|
|
MSFT,50,65.1
|
|
IBM,100,70.44
|
|
>>>
|
|
```
|
|
|
|
Create a class `HTMLTableFormatter` that generates output in HTML format:
|
|
|
|
```python
|
|
>>> import stock, reader, tableformat
|
|
>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)
|
|
>>> formatter = tableformat.HTMLTableFormatter()
|
|
>>> tableformat.print_table(portfolio, ['name','shares','price'], formatter)
|
|
<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>
|
|
<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>
|
|
<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>
|
|
<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>
|
|
<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>
|
|
<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>
|
|
<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>
|
|
<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>
|
|
>>>
|
|
```
|
|
|
|
## (d) Making it Easier To Choose
|
|
|
|
One problem with using inheritance is the added complexity of picking
|
|
different classes to use (e.g., remembering the names, using the right
|
|
`import` statements, etc.). A factory function can simplify this. Add
|
|
a function `create_formatter()` to your `tableformat.py` file that
|
|
allows a user to more easily make a formatter by specifying a format such as `'text'`, `'csv'`, or `'html'`. For example:
|
|
|
|
```python
|
|
>>> from tableformat import create_formatter, print_table
|
|
>>> formatter = create_formatter('html')
|
|
>>> print_table(portfolio, ['name','shares','price'], formatter)
|
|
<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>
|
|
<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>
|
|
<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>
|
|
<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>
|
|
<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>
|
|
<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>
|
|
<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>
|
|
<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>
|
|
>>>
|
|
```
|
|
|
|
**Discussion**
|
|
|
|
The `TableFormatter` class in this exercise is an example of something known
|
|
as an "Abstract Base Class." It's not something that's meant to be used directly.
|
|
Instead, it's serving as a kind of interface specification for a program component--in
|
|
this case the various output formats. Essentially, the code that produces the table
|
|
will be programmed against the abstract base class with the expectation that a user
|
|
will provide a suitable implementation. As long as all of the required methods
|
|
have been implemented, it should all just "work" (fingers crossed).
|
|
|
|
\[ [Solution](soln3_5.md) | [Index](index.md) | [Exercise 3.4](ex3_4.md) | [Exercise 3.6](ex3_6.md) \]
|
|
|
|
----
|
|
`>>>` Advanced Python Mastery
|
|
`...` A course by [dabeaz](https://www.dabeaz.com)
|
|
`...` Copyright 2007-2023
|
|
|
|
. This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
|