Classes and Objects are fundamental parts of Object Oriented Programming (OOP). Python is an OOP language and almost everything (including strings, numbers, lists) are seen as objects. In OOP, an object has also "fuctions" and they are called "methods". The object is an instance of a class: the class is a template that describes all the properties and methods while the object is a specific entity which has the class structure but filled with data. Details on Classes and Objects in Python can be found by clicking the button below.
This section discusses on how Python implements the main concepts that are the fundation of Object Oriented Programming: Encapsulation, Inheritance, Polymorphism and Abstraction.
Let's begin with Encapsulation which refers to the bundling of data, and the methods that operate on that data, into a single unit. Also, there are typically some mechanism that prevent the access to specific data or functions of the objects (the user should not necessarily know on how everything works, some implementative details are not exposed to the user) and these data and functions are typically used within specific functions or data which are exposed to the user.
In Python there is the convention of not accessing variables and methods which begin with an underscore "_": Python allows to access these entities but a programmer should know that they are not meant to be exposed to the users, they are "protected". A more restrictive version of this is the double underscore "__": when trying to access a variable or function with "__" at the beginning Python throws an error because does not find it. These entities are "private". It must be said that there are ways to still access these entities but the point is that the error is a way of explicitly telling the programmer that the entities are not meant to be accessed externally.
class Square: def __init__(self,side): #Square class with side variable self._side=side # Protected variable _side def _calculate_perimeter(self): # Method is protected return self._side*4 def print_perimeter(self): # Method is public print("The perimeter is",self._calculate_perimeter()) s=Square(5) print(s._side) # Prints 5, is defined as private but can be accessed print(s._calculate_perimeter()) # Prints 20, again private but accessible s.print_perimeter() # Public, it prints "The perimeter is 20" class Secure_Square: # Same class but with double underscore def __init__(self,side): #Square class with side variable self.__side=side # Private variable __side def __calculate_perimeter(self): # Method is private return self.__side*4 def print_perimeter(self): # Method is public print("The perimeter is",self.__calculate_perimeter()) s=Secure_Square(5) print(s._side) # Throws an error print(s._calculate_perimeter()) # Throws an error s.print_perimeter() # Public, it prints "The perimeter is 20"
The external public functions used let the user interact with the class are called setters and getters: they are able to modify values that are private/protected or access private/protected values. The example above shows a getter (it gets the private variable __side) while the example below shows both.
class Store_Number: def __init__(self,number): self.__number=number # Private __number def get_number(self): # Public getter return self.__number def set_number(self,number): # Public setter self.__number=number n=Store_Number(10) print(n.__number) # Throws an error print(n.get_number()) # Prints 10 n.set_number(15) print(n.get_number()) # Prints 15
The second concept discussed is Inheritance which allows a "child" class to inherit the variables, proprieties and methods of a "parent" class. This is done by simply creating the "child" class with the name of the "parent" class within round brackets. Inheritance is useful to avoid rewriting similar functionalities and efficiently write variations of a particular code. Every method re-written in the child class is overridden, otherwise the parent method is used. In case a child method needs to call a parent method it can simply use the name of the parent class, the dot and the name of the method. Another way of doing this is by using the function super()
which automatically selects the parent class without the need of writing a specific name.
#Parent class class Shape: def __init__(self,base=0,height=0): self.base=base self.height=height def print_message(self): print("This is a 2D shape!") def print_area(self): print("Shape not defined!") class Square(Shape): # Square is child of Shape def __init__(self,side=0): # Override the constructor print("This is a Square") self.side=side def print_area(self): #Override print_area method print("Square area is:",self.side**2) def print_perimeter(self): #Add new method not included in parent class print("Square perimeter is:",self.side*4) class Rectangle(Shape): # Rectangle is child of Shape def __init__(self,base=0,height=0): # Override the constructor print("This is a Rectangle") # The only difference we want is to add this sentence super().__init__(base,height) # Instead of rewriting the two variables we can use super() def print_area(self): #Override print_area method print("Rectangle area is:",self.base*self.height) shape=Shape() # No need arguments because the defaults are 0 shape.print_area() # Prints "Shape not defined!" shape.print_message() # Prints "This is a 2D shape!" square=Square(5) # Prints "This is a Square" square.print_area() # Prints "Square area is 25" square.print_perimeter() # Prints "Square perimeter is 20" square.print_message() # Prints again "This is a 2D shape!". This is inherited and not changed. rectangle=Rectangle(4,3) # Prints "This is a Rectangle" rectangle.print_area() # Prints "Rectangle area is 12" rectangle.print_message() # Prints again "This is a 2D shape!". This is inherited and not changed.
In the example above we have seen that a single parent class can be inherited by more than one child classes. It is true also the opposite, a single child class can inherit from more than one parent class: this can be done by including more than one parent class within round brackets.
class Parent1: #First parent class def Parent1_message(self): print("Inherited from Parent1") class Parent2: #Second parent class def Parent2_message(self): print("Inherited from Parent2") class Child(Parent1,Parent2): # This class inherits from both classes def Child_message(self): print("This method is not inherited!") c=Child() c.Parent1_message() #Prints "Inherited from Parent1" c.Parent2_message() #Prints "Inherited from Parent2" c.Child_message() #Prints "This method is not inherited!"
It is also possible to inherit multiple levels of classes: a class that inherits from another class which inherits from another class and so on.
class Class1: def __init__(self,value1=0): # Initialize value1 in Class1 self.class1_var=value1 def Print_value1(self): # Print value1 print(self.class1_var) class Class2(Class1): # Inherits from Class1 def __init__(self,value1=0,value2=0): #Initialize value2 in Class2 self.class2_var=value2 Class1.__init__(self,value1) #Call constructor in Class1 to initialize value1 def Print_value2(self): # Print value2 print(self.class2_var) class Class3(Class2): # Inherits from Class2 def __init__(self,value1=0,value2=0,value3=0): #Initialize value2 in Class2 self.class3_var=value3 Class2.__init__(self,value1,value2) #Call constructor in Class2 to initialize value2 which then calls constructor in Class1 for value1 def Print_value3(self): # Print value3 print(self.class3_var) c3=Class3(5,8,20) c3.Print_value3() # Prints 20 c3.Print_value2() # Prints 8 c3.Print_value1() # Prints 5
Polymorphism is another one of the core concepts of object-oriented programming and it is related to the situations where something occurs in different forms: it describes the possibility to access objects of different types through the same interface. This is important because in this way it is possible to re-use some code for several circumstances. In Python some in-built functions show already polymorphism, for example the function len()
can be used for lists or tuples or strings and in all these cases count the elements but these are different data types.
print(len("Hello")) # Prints 5, because it counts characters print(len((1,5,8))) # Prints 3, it counts elements of the tuple print(len(["hi",5,10,4.5])) # Prints 4, it counts elements of the list
Another typical example of polymorphism is the ability in Python to assign the same variable to different classes and, if they have the same method names, they can be directly called by the variable.
class Square: def message(self): print("This is a square!") class Triangle: def message(self): print("This is a triangle!") class Circle: def message(self): print("This is a circle!") s=Square() t=Triangle() c=Circle() shapes_list=[s,t,c] # List of these objects # The for loop use a single variable and a single method call # The output of the method depends on the list of objects for i in shapes_list: i.message() # This prints all three messages
The same can be done also through functions: a function expect an object with a specific method without concerning which is the object as long as the method exists.
class Square: def message(self): print("This is a square!") class Triangle: def message(self): print("This is a triangle!") class Circle: def message(self): print("This is a circle!") s=Square() t=Triangle() c=Circle() def Print_Message(shape): # The input is a "shape" shape.message() # This object needs to have a method "message()" Print_Message(s) # Prints "This is a square!" Print_Message(t) # Prints "This is a triangle!" Print_Message(c) # Prints "This is a circle!"
Another example of polymorphism is called "Method Overriding". This is related to inheritance and allows to change a method of the parent class within a child class in order to have a specific behavior.
class Power: # Class that raises number to the power of 1 def power(self,value): return value**1 class Power2(Power): # Class that raises to the power of 2 def power(self,value): # Overrides Power method return value**2 class Power3(Power): # Class that raises to the power of 3 def power(self,value): # Overrides Power method return value**3 p1=Power() p2=Power2() p3=Power3() print(p1.power(4)) # Prints 4 print(p2.power(4)) # Same method, prints 16 (4^2) print(p3.power(4)) # Same method, prints 64 (4^3)
Abstraction is the last core concept of OOP. This has to do, as the word says, with handling complexity and hiding unnecessary information: abstract the class to its principal features that need to be exposed to the user. In this way the user does not have to know how things are working inside but simply understanding which is the main structure and which methods to call. In Python this is done by importing the ABC library (ABC means "Abstract Base Classes") and the abstract class, which defines the main structure, needs to inherits ABC. Also, the abstract class only defines the methods names and arguments but not their content given that the content will be defined by the child classes that will inherit the abstract class: for this reason, the abstract class cannot be instantiated into an object.
#Import ABC and abstractmethod from abc import ABC, abstractmethod #This is the abstract class class Operation(ABC): @abstractmethod # This is a "decorator" which tells that the method is abstract def Calculate(self,value1,value2): # Abstract method which should calculate something pass class Add(Operation): def Calculate(self,value1,value2): # Adds value1 and value2 print(value1+value2) class Subtract(Operation): def Calculate(self,value1,value2): # Subtracts value2 from value1 print(value1-value2) class Multiply(Operation): def Calculate(self,value1,value2): # Multiplies value1 and value2 print(value1*value2) class Divide(Operation): def Calculate(self,value1,value2): # Divides value1 and value2 print(value1/value2) o=Operation() # This throws an error because it is an abstract class a=Add() s=Subtract() m=Multiply() d=Divide() #All these objects follow the same structure (there is an operation and needs to be calculated) #The user just needs to know that by "Calculating" the operation of the object is performed. a.Calculate(5,4) # Prints 9 s.Calculate(5,4) # Prints 1 m.Calculate(5,4) # Prints 20 d.Calculate(5,4) # Prints 1.25
This section explained how OOP concepts are implemented within Python. Use the console below and try some of the operations described.