Inheritance
One of the most important goals of the object-oriented approach to programming is the creation of stable, reliable, reusable code. If you had to create a new class for every kind of object you wanted to model, you would hardly have any reusable code. In Python and any other language that supports OOP, one class can inherit from another class. This means you can base a new class on an existing class; the new class inherits all of the attributes and behavior of the class it is based on. A new class can override any undesirable attributes or behavior of the class it inherits from, and it can add any new attributes or behavior that are appropriate. The original class is called the parent class, and the new class is a child of the parent class. The parent class is also called a superclass, and the child class is also called a subclass.
The child class inherits all attributes and behavior from the parent class, but any attributes that are defined in the child class are not available to the parent class. This may be obvious to many people, but it is worth stating. This also means a child class can override behavior of the parent class. If a child class defines a method that also appears in the parent class, objects of the child class will use the new method rather than the parent class method.
To better understand inheritance, let's look at an example of a class that can be based on the Rocket class.
The Space Shuttle class
If you wanted to model a space shuttle, you could write an entirely new class. But a space shuttle is just a special kind of rocket. Instead of writing an entirely new class, you can inherit all of the attributes and behavior of a Rocket, and then add a few appropriate attributes and behavior for a Shuttle.
One of the most significant characteristics of a space shuttle is that it can be reused. So the only difference we will add at this point is to record the number of flights the shutttle has completed. Everything else you need to know about a shuttle has already been coded into the Rocket class.
Here is what the Shuttle class looks like:
from math import sqrtclass Rocket(): # Rocket simulates a rocket ship for a game, # or a physics simulation. def __init__(self, x=0, y=0): # Each rocket has an (x,y) position. self.x = x self.y = y def move_rocket(self, x_increment=0, y_increment=1): # Move the rocket according to the paremeters given. # Default behavior is to move the rocket up one unit. self.x += x_increment self.y += y_increment def get_distance(self, other_rocket): # Calculates the distance from this rocket to another rocket, # and returns that value. distance = sqrt((self.x-other_rocket.x)**2+(self.y-other_rocket.y)**2) return distance
class Shuttle(Rocket):
# Shuttle simulates a space shuttle, which is really
# just a reusable rocket.
def __init__(self, x=0, y=0, flights_completed=0):
super().__init__(x, y)
self.flights_completed = flights_completed
shuttle = Shuttle(10,0,3)
print(shuttle)
When a new class is based on an existing class, you write the name of the parent class in parentheses when you define the new class:
class NewClass(ParentClass):
The __init__() function of the new class needs to call the __init__() function of the parent class. The __init__() function of the new class needs to accept all of the parameters required to build an object from the parent class, and these parameters need to be passed to the __init__() function of the parent class. The super().__init__() function takes care of this:
class NewClass(ParentClass): def __init__(self, arguments_new_class, arguments_parent_class):
super().__init__(arguments_parent_class)
# Code for initializing an object of the new class.
The super() function passes the self argument to the parent class automatically. You could also do this by explicitly naming the parent class when you call the __init__() function, but you then have to include the self argument manually:
class Shuttle(Rocket): # Shuttle simulates a space shuttle, which is really # just a reusable rocket. def __init__(self, x=0, y=0, flights_completed=0):
Rocket.__init__(self, x, y)
self.flights_completed = flights_completed
This might seem a little easier to read, but it is preferable to use the super() syntax. When you use super(), you don't need to explicitly name the parent class, so your code is more resilient to later changes. As you learn more about classes, you will be able to write child classes that inherit from multiple parent classes, and the super() function will call the parent classes' __init__() functions for you, in one line. This explicit approach to calling the parent class' __init__() function is included so that you will be less confused if you see it in someone else's code.
The output above shows that a new Shuttle object was created. This new Shuttle object can store the number of flights completed, but it also has all of the functionality of the Rocket class: it has a position that can be changed, and it can calculate the distance between itself and other rockets or shuttles. This can be demonstrated by creating several rockets and shuttles, and then finding the distance between one shuttle and all the other shuttles and rockets. This example uses a simple function called randint, which generates a random integer between a lower and upper bound, to determine the position of each rocket and shuttle:
from math import sqrt
from random import randint
class Rocket(): # Rocket simulates a rocket ship for a game, # or a physics simulation. def __init__(self, x=0, y=0): # Each rocket has an (x,y) position. self.x = x self.y = y def move_rocket(self, x_increment=0, y_increment=1): # Move the rocket according to the paremeters given. # Default behavior is to move the rocket up one unit. self.x += x_increment self.y += y_increment def get_distance(self, other_rocket): # Calculates the distance from this rocket to another rocket, # and returns that value. distance = sqrt((self.x-other_rocket.x)**2+(self.y-other_rocket.y)**2) return distance class Shuttle(Rocket): # Shuttle simulates a space shuttle, which is really # just a reusable rocket. def __init__(self, x=0, y=0, flights_completed=0): super().__init__(x, y) self.flights_completed = flights_completed
# Create several shuttles and rockets, with random positions.
# Shuttles have a random number of flights completed.
shuttles = []
for x in range(0,3):
x = randint(0,100)
y = randint(1,100)
flights_completed = randint(0,10)
shuttles.append(Shuttle(x, y, flights_completed))
rockets = []
for x in range(0,3):
x = randint(0,100)
y = randint(1,100)
rockets.append(Rocket(x, y))
# Show the number of flights completed for each shuttle.
for index, shuttle in enumerate(shuttles):
print("Shuttle {} has completed {} flights.".format(index,
shuttle.flights_completed))
print("\n")
# Show the distance from the first shuttle to all other shuttles.
first_shuttle = shuttles[0]
for index, shuttle in enumerate(shuttles):
distance = first_shuttle.get_distance(shuttle)
print("The first shuttle is {:f2} units away from shuttle {}.".format(distance,
index))
print("\n")
# Show the distance from the first shuttle to all other rockets.
for index, rocket in enumerate(rockets):
distance = first_shuttle.get_distance(rocket)
print("The first shuttle is {:f2} units away from rocket {}.".format(distance,
index))
Inheritance is a powerful feature of object-oriented programming. Using just what you have seen so far about classes, you can model an incredible variety of real-world and virtual phenomena with a high degree of accuracy. The code you write has the potential to be stable and reusable in a variety of applications.
Storing multiple classes in a module
A module is simply a file that contains one or more classes or functions, so the Shuttle class actually belongs in the rocket module as well:
# Save as rocket.pyfrom math import sqrtclass Rocket(): # Rocket simulates a rocket ship for a game, # or a physics simulation. def __init__(self, x=0, y=0): # Each rocket has an (x,y) position. self.x = x self.y = y def move_rocket(self, x_increment=0, y_increment=1): # Move the rocket according to the paremeters given. # Default behavior is to move the rocket up one unit. self.x += x_increment self.y += y_increment def get_distance(self, other_rocket): # Calculates the distance from this rocket to another rocket, # and returns that value. distance = sqrt((self.x-other_rocket.x)**2+(self.y-other_rocket.y)**2) return distance
class Shuttle(Rocket):
# Shuttle simulates a space shuttle, which is really
# just a reusable rocket.
def __init__(self, x=0, y=0, flights_completed=0):
super().__init__(x, y)
self.flights_completed = flights_completed
Now you can import the Rocket and the Shuttle class, and use them both in a clean uncluttered program file:
# Save as rocket_game.py
from rocket import Rocket, Shuttle
rocket = Rocket()print("The rocket is at ({}, {}).".format(rocket.x, rocket.y))
shuttle = Shuttle()
print("\nThe shuttle is at ({}, {}).".format(shuttle.x, shuttle.y))
print("The shuttle has completed {} flights.".format(shuttle.flights_completed))
The first line tells Python to import both the Rocket and the Shuttle classes from the rocket module. You don't have to import every class in a module; you can pick and choose the classes you care to use, and Python will only spend time processing those particular classes.
Three ways to import modules and classes
There are several ways to import modules and classes, and each has its own merits.
1) import module_name
The syntax for importing classes that was just shown:
from module_name import ClassName
is straightforward, and is used quite commonly. It allows you to use the class names directly in your program, so you have very clean and readable code. This can be a problem, however, if the names of the classes you are importing conflict with names that have already been used in the program you are working on. This is unlikely to happen in the short programs you have been seeing here, but if you were working on a larger program it is quite possible that the class you want to import from someone else's work would happen to have a name you have already used in your program. In this case, you can use simply import the module itself:
# Save as rocket_game.pyimport rocketrocket_0 = rocket.Rocket()print("The rocket is at ({}, {}).".format(rocket_0.x, rocket_0.y))
shuttle_0 = rocket.Shuttle()print("\nThe shuttle is at ({}, {}).".format(shuttle_0.x, shuttle_0.y))
print("The shuttle has completed {} flights.".format(shuttle_0.flights_completed))
The general syntax for this kind of import is:
import module_name
After this, classes are accessed using dot notation:
module_name.ClassName
This prevents some name conflicts. If you were reading carefully however, you might have noticed that the variable name rocket in the previous example had to be changed because it has the same name as the module itself. This is not good, because in a longer program that could mean a lot of renaming.
2) import module_name as local_module_name
There is another syntax for imports that is quite useful:
import module_name as local_module_name
When you are importing a module into one of your projects, you are free to choose any name you want for the module in your project. So the last example could be rewritten in a way that the variable name rocket would not need to be changed:
# Save as rocket_game.pyimport rocket as rocket_modulerocket = rocket_module.Rocket()print("The rocket is at ({}, {}).".format(rocket.x, rocket.y))shuttle = rocket_module.Shuttle()print("\nThe shuttle is at ({}, {}).".format(shuttle.x, shuttle.y))
print("The shuttle has completed {} flights.".format(shuttle.flights_completed))
This approach is often used to shorten the name of the module, so you don't have to type a long module name before each class name that you want to use. But it is easy to shorten a name so much that you force people reading your code to scroll to the top of your file and see what the shortened name stands for. In this example,
import rocket as rocket_module
leads to much more readable code than something like:
import rocket as r
3) from module_name import *
There is one more import syntax that you should be aware of, but you should probably avoid using. This syntax imports all of the available classes and functions in a module:
from module_name import *
This is not recommended, for a couple reasons. First of all, you may have no idea what all the names of the classes and functions in a module are. If you accidentally give one of your variables the same name as a name from the module, you will have naming conflicts. Also, you may be importing way more code into your program than you need.
If you really need all the functions and classes from a module, just import the module and use the module_name.ClassName syntax in your program.