Data Science and Computing with Python for Pilots and Flight Test Engineers
Classes
A somewhat advanced topic, but of great importance for so called object-oriented programming, are classes. Classes allow us to create our own self-defined new data structures, which can contain:
- data (those are variables and constants which then belong to the class object)
- functions (called methods, when they are part of a class definition)
An object is an instance of a class (i.e. a particular realization of it, just as variable \(x\) (with \(x=3\)) is a realization of an integer type). The data members of the class then belong to this particular object, and this object also has methods that can be called. We saw how methods can be attached to an object already, when we worked with lists and used their append feature.
Consider the following example. Below we define a new data type, a class called Cars. (As an unrelated side note, notice how methods in a class must always list “self” as their first argument, even though this is skipped when calling the method eventually from the main code.)
class Cars:
# Method 1:
def make_cars(self):
carlist = ["Ford", "Mazda", "Volkswagen"] # this is just a local variable of function make_cars.
self.fancy_carlist = ["Audi", "Porsche"] # this is class data attached to the class Cars (because of the "self.")
return carlist
# Method 2:
def print_cars(self, list_of_cars=None):
if list_of_cars is not None:
print(list_of_cars)
else:
print(self.fancy_carlist)
return
The class definition contains two methods, make_cars() and print_cars(). These are functions that belong to the class. Each class object that we instantiate, will have these methods attached. The methods can be called by appending a dot and the method name after the name of the object (see example below).
The first method, make_cars() also creates a data member of the Cars class, a variable called fancy_carlist. We can see that this is a data member of the class and not just a local variable of method make_cars(), because fancy_carlist appears as self.fancy_carlist. (Self points to the class object itself.)
As an unrelated side note, notice how methods of a class must always list “self” as their first argument. This argument is skipped, when the method is eventually called from the main code.
The following code uses the above Cars class to actually do something (it is the main program in this example):
# Instantiate class object (car_factory now becomes an instance of the class):
car_factory = Cars()
# Call the make_cars() method of the class to make a list of cars,
# which is saved in the variable mycars (mycars is not part of the class):
mycars = car_factory.make_cars()
# car_factory now also contains the variable fancy_carlist (which is of type list) as a data member:
print("Class Data:", car_factory.fancy_carlist)
# Print cars made (several options):
car_factory.print_cars()
car_factory.print_cars(mycars)
car_factory.print_cars(car_factory.fancy_carlist)
# car_factory.print_cars(carlist)
# The last line above is not allowed and would produce an error (therefore it is commented out), because the variable "carlist" is a local variabe of function make_cars() and stops existing, once the method exits, unlike fancy_carlist, which is data attached to the class Cars.
In the first code cell above, we define the class, but nothing happens yet. In the second cell, we use it. A class object is called an instance of a class. Creating the object (essentially a “variable” having the class as its data type) is called instantiation. Above in the second cell, “car_factory” is an instance of class “Cars”. “car_factory” is an object. This object can contain variables (data members) and functions (methods).
After instantiation, we call the method “make_cars()”, which belongs to the object car_factory, because “make_cars()” is part of the “Cars” class, and car_factory is an object of that class. This method “make_cars()” creates some data in the class, which is called “fancy_carlist” and can be accessed from the main code (second cell) with “car_factory.fancy_carlist”. Another variable, called list “carlist”, is also created by the method “make_cars()”, but it is not attached to the Car class as data, but rather just stored in a local variable of the function “make_cars”. It ceases to exist as soon as the function “make_cars” finishes running. The way we can tell this is that “carlist” does not have a “self.” in front of its name. The “self” in the class definition refers to the class itself, which the variable (or method) belongs to and attaches the variable to the class as a data member. This distinguishes these variables from mere local variables, which exist within the methods but are not attached as data to the class object.
The method make_cars() thus creates two lists. One is put into the local variable “carlist” which is forgotten, once the method exits (we therefore give it as a return value – an output of the function make_cars() – and catch it with the global “mycars” variable in the main code). The other list is the fancy_carlist. fancy_carlist is data that belongs to the Cars class and is not forgotten, when make_cars() finishes funning, as long as the object car_factory exist, to which fancy_carlist is attached as a data member.
Constructor and Destructor
Imagine that in the above example we want the method make_cars() to be called automatically, when the class Cars is instantiated, i.e. when the object car_factory is created. This is useful, if we know that we will always want to call this method, before starting to working with an instance of this class. We can achieve this by renaming the method make_cars to “__init__()”, i.e. “init” with two underscores in front and behind. This method now becomes the constructor of the class. It gets automatically called, when a new object of a class is instantiated. Similarly, one can create a destructor, which is automatically called, when a class object ceases to exist.
Below is a slightly modified example of the above Cars class, called BetterCars. In has a constructor method now, which is automatically called, when on instance of the class (i.e. an object) is created.
class BetterCars:
def __init__(self):
self.carlist = ["Ford, Mazda, Volkswagen"]
self.fancy_carlist = ["Audi", "Porsche"]
return
def print_cars(self, list_of_cars=None):
if list_of_cars is not None:
print(list_of_cars)
else:
print(self.carlist, self.fancy_carlist)
return
# Instantiate class object:
car_factory2 = BetterCars()
# mycars now contain the following data right away:
print("Class Data:", car_factory2.carlist, car_factory2.fancy_carlist)
# Print cars made (several options):
car_factory2.print_cars()
car_factory2.print_cars(car_factory2.carlist)
car_factory2.print_cars(car_factory2.fancy_carlist)
In the above example, we have made the variable carlist also part of the class data, because we did not want the constructor to return any variables.
To be continued…