Let’s change our example so that both Circle and Rectangle are derived from such a general class called Geometry. This class will be an abstract class in the sense that it is not intended to be used for creating objects from. Its purpose is to introduce properties and templates for methods that all geometric classes in our project have in common.
class Geometry(): def __init__(self, x = 0.0, y = 0.0): self.x = x self.y = y def computeArea(self): pass def computePerimeter(self): pass def move(self, deltaX, deltaY): self.x += deltaX self.y += deltaY def __str__(self): return 'Abstract class Geometry should not be instantiated and derived classes should override this method!'
The constructor of class Geometry looks pretty normal, it just initializes the instance variables that all our geometry objects have in common, namely x and y coordinates to describe their location in our 2D coordinate system. This is followed by the definitions of the methods computeArea(), computePerimeter(), move(…), and __str__() that all geometry objects should support. For move(…), we can already provide an implementation because it is entirely based on the x and y instance variables and works in the same way for all geometry objects. That means the derived classes for Circle and Rectangle will not need to provide their own implementation. In contrast, you cannot compute an area or perimeter in a meaningful way just from the position of the object. Therefore, we used the keyword pass to indicate that we are leaving the body of the computeArea() and computePerimeter() methods intentionally empty. These methods will have to be overridden in the definitions of the derived classes with implementations of their specialized behavior. We could have done the same for __str__() but instead we return a warning message that this class should not have been instantiated.
It is worth mentioning that, in many object-oriented programming languages, the concepts of an abstract class (= a class that cannot be instantiated) and an abstract method (= a method that must be overridden in every subclass that can be instantiated) are built into the language. That means there exist special keywords to declare a class or method to be abstract and then it is impossible to create an object of that class or a subclass of it that does not provide an implementation for the abstract methods. In Python, this has been added on top of the language via a module in the standard library called abc [1] (for abstract base classes). Although we won’t be using it in this course, it is a good idea to check it out and use it if you get involved in larger Python projects. This Abstract Classes page [2] is a good source for learning more.
Here is our new definition for class Circle that is now derived from class Geometry. We also use a few commands at the end to create and use a new Circle object of this class to make sure everything is indeed working as before:
import math class Circle(Geometry): def __init__(self, x = 0.0, y = 0.0, radius = 1.0): super(Circle,self).__init__(x,y) self.radius = radius def computeArea(self): return math.pi * self.radius ** 2 def computePerimeter (self): return 2 * math.pi * self.radius def __str__(self): return 'Circle with coordinates {0}, {1} and radius {2}'.format(self.x, self.y, self.radius) circle1 = Circle(10, 10, 10) print(circle1.computeArea()) print(circle1.computePerimeter()) circle1.move(2,2) print(circle1)
Here are the things we needed to do in the code:
The new definition of class Rectangle, now derived from Geometry, looks very much the same as that of Circle if you replace “Circle” with “Rectangle”. Only the implementations of the overridden methods look different, using the versions specific for rectangles.
class Rectangle(Geometry): def __init__(self, x = 0.0, y = 0.0, width = 1.0, height = 1.0): super(Rectangle, self).__init__(x,y) self.width = width self.height = height def computeArea(self): return self.width * self.height def computePerimeter (self): return 2 * (self.width + self.height) def __str__(self): return 'Rectangle with coordinates {0}, {1}, width {2} and height {3}'.format(self.x, self.y, self.width, self.height ) rectangle1 = Rectangle(15,20,4,5) print(rectangle1.computeArea()) print(rectangle1.computePerimeter()) rectangle1.move(2,2) print(rectangle1)