\[ [Index](index.md) | [Exercise 3.6](ex3_6.md) | [Exercise 3.8](ex3_8.md) \]

# Exercise 3.7

*Objectives:*

- Type checking and interfaces
- Abstract base classes

*Files Modified:* `tableformat.py`

In [Exercise 3.5](ex3_5.md), we modified the `tableformat.py` file to have a `TableFormatter`
class and to use various subclasses for different output formats.  In this exercise, we extend that
code a bit more.

## (a) Interfaces and type checking

Modify the `print_table()` function  so that it checks if the
supplied formatter instance inherits from `TableFormatter`.  If
not, raise a `TypeError`.

Your new code should catch situations like this:

```python
>>> import stock, reader, tableformat
>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)
>>> class MyFormatter:
        def headings(self,headers): pass
        def row(self,rowdata): pass

>>> tableformat.print_table(portfolio, ['name','shares','price'], MyFormatter())
Traceback (most recent call last):
...
TypeError: Expected a TableFormatter
>>> 
```

Adding a check like this might add some degree of safety to the program.   However you should
still be aware that type-checking is rather weak in Python.  There is no guarantee that the
object passed as a formatter will work correctly even if it happens to inherit from the
proper base class.  This next part addresses that issue.

## (b) Abstract Base Classes

Modify the `TableFormatter` base class so that it is defined as a proper
abstract base class using the `abc` module.   Once you have done that, try
this experiment:

```python
>>> class NewFormatter(TableFormatter):
        def headers(self, headings):
            pass
        def row(self, rowdata):
            pass

>>> f = NewFormatter()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class NewFormatter with abstract methods headings
>>> 
```

Here, the abstract base class caught a spelling error in the class--the fact that
the `headings()` method was incorrectly given as `headers()`.

## (c) Algorithm Template Classes

The file `reader.py` contains two functions, `read_csv_as_dicts()` and `read_csv_as_instances()`.
Both of those functions are almost identical--there is just one tiny bit of code that's
different.  Maybe that code could be consolidated into a class definition of some sort.
Add the following class to the `reader.py` file:

```python
# reader.py


import csv
from abc import ABC, abstractmethod

class CSVParser(ABC):

    def parse(self, filename):
        records = []
        with open(filename) as f:
            rows = csv.reader(f)
            headers = next(rows)
            for row in rows:
                record = self.make_record(headers, row)
                records.append(record)
        return records

    @abstractmethod
    def make_record(self, headers, row):
        pass
```

This code provides a shell (or template) of the CSV parsing functionality.   To use it, you subclass it, add
any additional attributes you might need, and redefine the `make_record()` method. For example:

```python
class DictCSVParser(CSVParser):
    def __init__(self, types):
        self.types = types

    def make_record(self, headers, row):
        return { name: func(val) for name, func, val in zip(headers, self.types, row) }

class InstanceCSVParser(CSVParser):
    def __init__(self, cls):
        self.cls = cls

    def make_record(self, headers, row):
        return self.cls.from_row(row)
```

Add the above classes to the `reader.py` file.  Here's how you would use one of them:

```python
>>> from reader import DictCSVParser
>>> parser = DictCSVParser([str, int, float])
>>> port = parser.parse('Data/portfolio.csv')
>>>
```

It works, but it's kind of annoying.  To fix this, reimplement the `read_csv_as_dicts()` and
`read_csv_as_instances()` functions to use these classes.   Your refactored code should work
exactly the same way that it did before.  For example:

```python
>>> import reader
>>> import stock
>>> port = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)
>>>
```

\[ [Solution](soln3_7.md) | [Index](index.md) | [Exercise 3.6](ex3_6.md) | [Exercise 3.8](ex3_8.md) \]

----
`>>>` Advanced Python Mastery  
`...` A course by [dabeaz](https://www.dabeaz.com)  
`...` Copyright 2007-2023  

![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
