Subclasses and Inheritance in Python

Classes are designed as language features that can represent a hierarchy of information or structure. A class can be used to define another, and properties from the first class are passed on (inherited) by the other. A class that is based on another in this way is called a subclass, and there are many types: a pet class with dogs and cats as special cases; a polygon having triangles and rectangles as subclasses; a dessert class, having subclasses pie, cake, and cookie; and even the initial example in this chapter of a man and a woman class and the person class that they can be derived from. A subclass is a more specific case of the superclass (or parent class) on which it is based.

The examples above are for explanation, and are not really useful as software components, which begs a question about whether subclasses are really useful things. They are, but it requires non-trivial examples to demonstrate this.

1.  Non-Trivial Example: Objects in a Video Game

To some degree, all objects in a game have some things in common. They are things that can interact with other game objects; they have a position within the volume of space defined by the game and they have a visual appearance. Thus, a description of a class that could implement a game object would include:

class gobject:

position = (0, 0, 0)        # Object position in 3D

visual = None               # Graphics that represent

                            # the object

def init (self, pos, vis)

def getPosition (self):

def setPosition(self, p):

def setVisual(self, v):

def draw (self):

Anyone who has played a video game knows that some of the objects can move while others cannot. Objects that move can have their position change, and the position has to be updated regularly. An object that can move can have a speed and a method that updates their position; otherwise it is like a gobject. This is a good case for a subclass:

class mobject (gobject):

speed = (0,  0,  0)  # Speed in pixels per frame

                     # the x,y,z

directions def init (self, s)

def getSpeed(self):

def setSpeed(self, s):

def move(self):

def collision(self, gobject):

The syntax of this has the superclass gobject as a parameter (apparently) of the subclass mobject being defined. If an instance of a gobject is created, its__ init__ method is called and the resulting reference has access to all of the methods in the gobject definition, just as one would expect. If an instance of mobject is created, the__ init__ method of mobject is called, but not that of gobject. Nonetheless, all properties and methods of both classes are available through the mobject reference. The following is legal:

m = mobject (  (12, 0, 0))  # Create mboject with

                            # speed (12,0,0)

m.draw()                    # Draw this object

This code is acceptable even though an mobject does not possess a method draw(); the method defined in the parent class is accessible and will be used.

When the mobject is created, it is also a gobject, and all of the variables and methods belonging to a gobject are defined also. However, the __init__() meth­od for gobject is not called unless the mobject __init__() method does so. This means that, for the mobject, the values of position and visual are not specified by the constructor and will take the default values they were given in the gobject class. If no such value was given, they will be undefined, and an error will occur if they are referenced.

Calling the__ init__ () method of the parent class can be done as follows:

super(). init ((10,10,10), None)

In this instance, the constructor for gobject is called, passing a position and a visual. This would normally be done only in the __init__() of the subclass.

Now consider the following code. The methods are mainly stubs that print a message, but the output of the program is instructive:

The output from this is

Attempting to call g.move() would fail because there is no move() method within the gobject class. Hence, if an object was passed to a function that would attempt to move it, it would be critical to know whether the parameter passed was a gobject or an mobject. Consider a method that moves an object x out of the path of an mobject instance if it can, or changes the path of the mobject if it cannot. This method, named dodge(), might do the following:

def dodge self, (x):

c = x.getPosition()

c = c + (dx, dy, 0)

x.setPosition (c)

However, if the parameter is an instance of a gobject, then it should not be moved. The function isinstance() can be used to determine this. The result of

isinstance (x, gobject)

is True if x is a gobject and False otherwise. If False, then it cannot be moved and the dodge() method will have to move the current mobject out of the way instead:

def dodge self, (x):

if isinstance(x, gobject):

self.position = self.position + (dx, dy, 0)

else:

c = x.getPosition()

c = c + (dx, dy, 0)

x.setPosition (c)

 

Source: Parker James R. (2021), Python: An Introduction to Programming, Mercury Learning and Information; Second edition.

Leave a Reply

Your email address will not be published. Required fields are marked *