\[ [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 "", line 1, in 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/)