This notebook includes a general introduction to object oriented programming. Python3 is used but most of the code is compatible also with Python2.
The OOP main advantages are the encapsulation, making the code more modular, and the reuse, less code is needed.
The minimal class in Python is:
In [4]:
class Atom():
pass
Classes are used to create objects that will be how the logic of the software programa is implemented.
In [5]:
atom = Atom()
In this sample, the class does not receive any kind of configuration (params). Objects are created normally using params, which define the initial state of the object. In this example, a Carbon object is created with 6 electrons and 6 protons.
In [6]:
class Atom():
def __init__(self, electrons, protons):
self.electrons = electrons
self.protons = protons
carbon = Atom(electrons = 6, protons = 6)
To create a new Object using initial params, a special method must be defined in the Class: __init__. This method will be called automatically when the object is created, and the params used in the object creation are passed to this init method.
The first param of the __init__ methods, and the first param in all the methods of a class, is the param self. This param represent the current object in which the method is called.
To add features to a class, new methods are added. These are normal python methods that have as the first param the self variable, pointing to the object in which the method was called.
Let's add two new methods to the Atom class to query the electrons and protons numbers for an atom.
In [7]:
class Atom():
def __init__(self, electrons, protons):
self.electrons = electrons
self.protons = protons
def get_electrons(self):
return self.electrons
def get_protons(self):
return self.protons
carbon = Atom(electrons = 6, protons = 6)
print("The carbon atom has %i electrons" % carbon.get_electrons())
print("The carbon atom has %i protons" % carbon.get_electrons())
A class could be defined reusing (specializing) another class (its base class). Let's create a new class to work specifically with Carbon atoms.
In [8]:
class Carbon(Atom):
def __init__(self):
self.electrons = 6
self.protons = 6
carbon = Carbon()
print("The carbon atom has %i electrons" % carbon.get_electrons())
print("The carbon atom has %i protons" % carbon.get_electrons())
The new Carbon class is an specilization of the base class Atom. It already know the numbers of electrons and protons, so they are not needed in the init params. And all the methods included in the base Atom base class, are also available in this new class.
The Carbon class can redefine the methods from the base class. In the above sample, it just redefines the __init__ method, but the rest of methods are used as they are in the Atom base class (get_electrons and get_protons).
In this way, a selective reuse of code can be implemented. And new methods can be added to complete the behaviour needed for a class.
The Carbon atom has 11 isotopes but the most common is Carbon12, that it has 6 neutrons in the atom's nucleus.
A new class can be defined to represent this Carnon12 isotope.
In [9]:
class Carbon12(Carbon):
def __init__(self):
self.electrons = 6
self.protons = 6
self.neutrons = 6
All the atoms have neutrons in the nucleus, so this param must be also added to the class Atom that models what an Atom is. And also, a method to get the value. In this way, it is not needed to create a new class for each isotope because this information can be stored in the Atom class directly.
Using this approach the new class Atom is:
In [10]:
class Atom():
def __init__(self, electrons, protons, neutrons):
self.electrons = electrons
self.protons = protons
self.neutrons = neutrons
def get_electrons(self):
return self.electrons
def get_protons(self):
return self.protons
def get_neutrons(self):
return self.neutrons
class Carbon(Atom):
def __init__(self, neutrons=6):
self.electrons = 6
self.protons = 6
self.neutrons = neutrons
Now, Atoms by default are created with 6 neutrons, like Carbon12. So this is the default isotope generated when creating new Atoms. But this value can be chnaged just passing a different value in the constructor.
In [11]:
carbon13 = Carbon(neutrons=7)
print(carbon13.get_neutrons())
Analyzing the Carbon class, the number or electros and protons is always the same, no matter what is the carbon isotope object we are creating. So this data is not object specific but class specific. All the objects created from this Carbon class will have the same values for electros and protons.
To these variables that are related to the class, and that are not defined per object, we call the static variables. They are declared at class level in this way:
In [12]:
class Atom():
electrons = None
protons = None
def __init__(self, neutrons):
self.neutrons = neutrons
@classmethod
def get_electrons(cls):
print(cls)
return cls.electrons
@classmethod
def get_protons(cls):
return cls.protons
def get_neutrons(self):
return self.neutrons
class Carbon(Atom):
electrons = 6
protons = 6
def __init__(self, neutrons=6):
self.neutrons = neutrons
In [13]:
carbon = Carbon()
print(Carbon.get_electrons())
print(carbon.get_electrons())
In [14]:
class Atom():
electrons = None
protons = None
def __init__(self, neutrons=None):
self.neutrons = neutrons
self.__atom_private()
@classmethod
def get_electrons(cls):
print(cls)
return cls.electrons
@classmethod
def get_protons(cls):
return cls.protons
def get_neutrons(self):
return self.neutrons
def __atom_private(self):
print("Atom private method")
class Carbon(Atom):
electrons = 6
protons = 6
def __init__(self, neutrons=6):
self.neutrons = neutrons
self.__atom_private()
atom = Atom()
carbon = Carbon()