5.7. Abstract Class

5.7.1. Rationale

  • Cannot instantiate

  • Possible to indicate which method must be implemented by child

  • Inheriting class must implement all methods

  • Some methods can have implementation

abstract class

Class which can only be inherited, not instantiated

abstract method

Method must be implemented in a subclass

abstract static method

Static method which must be implemented in a subclass

5.7.2. Example

from abc import ABC, abstractmethod


class Astronaut(ABC):

    @abstractmethod
    def say_hello(self):
        pass


astro = Astronaut()
# Traceback (most recent call last):
#     ...
# TypeError: Can't instantiate abstract class Astronaut with abstract method say_hello
from abc import ABCMeta, abstractmethod


class Astronaut(metaclass=ABCMeta):

    @abstractmethod
    def say_hello(self):
        pass


astro = Astronaut()
# Traceback (most recent call last):
#     ...
# TypeError: Can't instantiate abstract class Astronaut with abstract method say_hello

5.7.3. Errors

Listing 5.99. In order to use Abstract Base Class you must create abstract method. Otherwise it won't prevent from instantiating.
from abc import ABC

class Astronaut(ABC):
    pass

astro = Astronaut()
print('no errors')
# no errors
Listing 5.100. In order to use Abstract Base Class you must create abstract method. Otherwise it won't prevent from instantiating.
from abc import ABCMeta

class Astronaut(metaclass=ABCMeta):
    pass

astro = Astronaut()
print('no errors')
# no errors
Listing 5.101. Must implement all abstract methods
from abc import ABCMeta, abstractmethod

class Human(metaclass=ABCMeta):
    @abstractmethod
    def say_hello(self):
        pass

class Astronaut(Human):
    pass


astro = Astronaut()
# Traceback (most recent call last):
#     ...
# TypeError: Can't instantiate abstract class Astronaut with abstract method say_hello
Listing 5.102. abc is common name and it is very easy to create file, variable lub module with the same name as the library, hence overwrite it. In case of error. Check all entries in sys.path or sys.modules['abc'] to find what is overwriting it.
from pprint import pprint
import sys


sys.modules['abc']
# <module 'abc' from '/usr/local/Cellar/python@3.8/3.8.3/Frameworks/Python.framework/Versions/3.8/lib/python3.8/abc.py'>

pprint(sys.path)
# ['/Users/matt/Developer/book-python/advanced/oop/solution',
#   '/Applications/PyCharm 2020.2 EAP.app/Contents/plugins/python/helpers/pydev',
#   '/Users/matt/Developer/book-python',
#   '/Users/matt/Developer/book-python/_tmp',
#   '/Applications/PyCharm 2020.2 '
#   'EAP.app/Contents/plugins/python/helpers/pycharm_display',
#   '/Applications/PyCharm 2020.2 '
#   'EAP.app/Contents/plugins/python/helpers/third_party/thriftpy',
#   '/Applications/PyCharm 2020.2 EAP.app/Contents/plugins/python/helpers/pydev',
#   '/usr/local/Cellar/python@3.8/3.8.3/Frameworks/Python.framework/Versions/3.8/lib/python38.zip',
#   '/usr/local/Cellar/python@3.8/3.8.3/Frameworks/Python.framework/Versions/3.8/lib/python3.8',
#   '/usr/local/Cellar/python@3.8/3.8.3/Frameworks/Python.framework/Versions/3.8/lib/python3.8/lib-dynload',
#   '/Users/matt/Developer/book-python/.venv-3.8.3/lib/python3.8/site-packages',
#   '/Applications/PyCharm 2020.2 '
#   'EAP.app/Contents/plugins/python/helpers/pycharm_matplotlib_backend',
#   '/Users/matt/Developer/book-python',
#   '/Users/matt/Developer/book-python/_tmp']

5.7.4. Use Cases

Listing 5.103. Abstract Class
from abc import ABC, abstractmethod


class Document(ABC):
    def __init__(self, filename):
        self.filename = filename
        self.content = self._read_file_content(filename)

    def _read_file_content(self):
        with open(self.filename, mode='rb') as file:
            return file.read()

    @abstractmethod
    def display(self, content):
        pass


class PDFDocument(Document):
    def display(self):
        # display self.content as PDF Document

class WordDocument(Document):
    def display(self):
        # display self.content as Word Document


file1 = PDFDocument('filename.pdf')
file1.display()

file2 = Document('filename.txt')
# Traceback (most recent call last):
#     ...
# TypeError: Can't instantiate abstract class Document with abstract method display

5.7.5. Assignments

5.7.5.1. OOP Abstract Define

  • Assignment name: OOP Abstract Define

  • Last update: 2020-10-01

  • Complexity level: easy

  • Lines of code to write: 10 lines

  • Estimated time of completion: 5 min

  • Solution: solution/oop_abstract_define.py

English
  1. Create abstract class Iris

  2. Create abstract method get_name() in Iris

  3. Create class Setosa inheriting from Iris

  4. Try to create instance of a class Setosa

  5. Try to create instance of a class Iris

  6. Compare result with "Output" section (see below)

Polish
  1. Stwórz klasę abstrakcyjną Iris

  2. Stwórz metodę abstrakcyjną get_name() w Iris

  3. Stwórz klasę Setosa dziedziczące po Iris

  4. Spróbuj stworzyć instancje klasy Setosa

  5. Spróbuj stworzyć instancję klasy Iris

  6. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Note

  • Last line of doctest, second to last word of TypeError message

  • In Python 3.7, 3.8 there is "methods" word in doctest

  • In Python 3.9 there is "method" word in doctest

  • So it differs by "s" at the end of "method" word

Output
>>> iris = Iris()
Traceback (most recent call last):
  ...
TypeError: Can't instantiate abstract class Iris with abstract method get_name

>>> setosa = Setosa()

5.7.5.2. OOP Abstract Interface

  • Assignment name: OOP Abstract Interface

  • Last update: 2020-10-14

  • Complexity level: easy

  • Lines of code to write: 14 lines

  • Estimated time of completion: 13 min

  • Solution: solution/oop_abstract_interface.py

English
  1. Define abstract class IrisAbstract

  2. Attributes: sepal_length, sepal_width, petal_length, petal_width

  3. Methods: sum(), len(), mean()

  4. All methods and constructor must raise exception NotImplementedError

  5. Create class Setosa inheriting from IrisInterface

  6. Implement interface

  7. Compare result with "Output" section (see below)

Polish
  1. Zdefiniuj klasę abstrakcyjną IrisAbstract

  2. Attributes: sepal_length, sepal_width, petal_length, petal_width

  3. Metody: sum(), len(), mean()

  4. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Output
>>> from inspect import isabstract
>>> assert isabstract(IrisAbstract)
>>> assert hasattr(IrisAbstract, 'mean')
>>> assert hasattr(IrisAbstract, 'sum')
>>> assert hasattr(IrisAbstract, 'len')