Type Checking and Interfaces

PythonPythonBeginner
Practice Now

This tutorial is from open-source community. Access the source code

Introduction

Objectives:

  • Type checking and interfaces
  • Abstract base classes

Files Modified: tableformat.py

In Exercise 3.5, 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.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("`Python`")) -.-> python/BasicConceptsGroup(["`Basic Concepts`"]) python(("`Python`")) -.-> python/FileHandlingGroup(["`File Handling`"]) python(("`Python`")) -.-> python/ControlFlowGroup(["`Control Flow`"]) python(("`Python`")) -.-> python/DataStructuresGroup(["`Data Structures`"]) python(("`Python`")) -.-> python/FunctionsGroup(["`Functions`"]) python(("`Python`")) -.-> python/ModulesandPackagesGroup(["`Modules and Packages`"]) python(("`Python`")) -.-> python/ObjectOrientedProgrammingGroup(["`Object-Oriented Programming`"]) python(("`Python`")) -.-> python/ErrorandExceptionHandlingGroup(["`Error and Exception Handling`"]) python(("`Python`")) -.-> python/AdvancedTopicsGroup(["`Advanced Topics`"]) python/BasicConceptsGroup -.-> python/comments("`Comments`") python/FileHandlingGroup -.-> python/with_statement("`Using with Statement`") python/BasicConceptsGroup -.-> python/variables_data_types("`Variables and Data Types`") python/BasicConceptsGroup -.-> python/numeric_types("`Numeric Types`") python/BasicConceptsGroup -.-> python/strings("`Strings`") python/BasicConceptsGroup -.-> python/type_conversion("`Type Conversion`") python/ControlFlowGroup -.-> python/conditional_statements("`Conditional Statements`") python/ControlFlowGroup -.-> python/for_loops("`For Loops`") python/ControlFlowGroup -.-> python/list_comprehensions("`List Comprehensions`") python/DataStructuresGroup -.-> python/lists("`Lists`") python/DataStructuresGroup -.-> python/tuples("`Tuples`") python/DataStructuresGroup -.-> python/dictionaries("`Dictionaries`") python/FunctionsGroup -.-> python/function_definition("`Function Definition`") python/ModulesandPackagesGroup -.-> python/importing_modules("`Importing Modules`") python/ModulesandPackagesGroup -.-> python/using_packages("`Using Packages`") python/ModulesandPackagesGroup -.-> python/standard_libraries("`Common Standard Libraries`") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("`Classes and Objects`") python/ObjectOrientedProgrammingGroup -.-> python/constructor("`Constructor`") python/ObjectOrientedProgrammingGroup -.-> python/polymorphism("`Polymorphism`") python/ObjectOrientedProgrammingGroup -.-> python/encapsulation("`Encapsulation`") python/ErrorandExceptionHandlingGroup -.-> python/raising_exceptions("`Raising Exceptions`") python/FileHandlingGroup -.-> python/file_opening_closing("`Opening and Closing Files`") python/AdvancedTopicsGroup -.-> python/iterators("`Iterators`") python/BasicConceptsGroup -.-> python/python_shell("`Python Shell`") python/FunctionsGroup -.-> python/build_in_functions("`Build-in Functions`") subgraph Lab Skills python/comments -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/with_statement -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/variables_data_types -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/numeric_types -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/strings -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/type_conversion -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/conditional_statements -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/for_loops -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/list_comprehensions -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/lists -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/tuples -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/dictionaries -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/function_definition -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/importing_modules -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/using_packages -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/standard_libraries -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/classes_objects -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/constructor -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/polymorphism -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/encapsulation -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/raising_exceptions -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/file_opening_closing -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/iterators -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/python_shell -.-> lab-132497{{"`Type Checking and Interfaces`"}} python/build_in_functions -.-> lab-132497{{"`Type Checking and Interfaces`"}} end

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:

>>> import stock, reader, tableformat
>>> portfolio = reader.read_csv_as_instances('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.

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:

>>> 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().

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:

## 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:

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:

>>> from reader import DictCSVParser
>>> parser = DictCSVParser([str, int, float])
>>> port = parser.parse('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:

>>> import reader
>>> import stock
>>> port = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>>

Summary

Congratulations! You have completed the Type Checking and Interfaces lab. You can practice more labs in LabEx to improve your skills.

Other Python Tutorials you may like