Day 42: Object-Orientation in Python - Summarized
John Enad
Posted on May 29, 2023
Today, I'll write about the fundamentals of Python Object-Oriented Programming (OOP).
Simply put, Object-Oriented Programming (OOP) is a programming paradigm that uses 'objects' - instances of classes, which are like blueprints for objects. OOP focuses on utilizing these objects to design and implement software.
It allows for a clear modular structure of programs which therefore makes it good for defining abstract data types in large programs. It is also good for code reusability and to reduce complexity.
There are four basic concepts in OOP: Encapsulation, Inheritance, Polymorphism, and Abstraction. And I sometimes have a hard time explaining them in simple terms so I'm writing it down so I can come back here every time I need to look it up. But first, we need to talk about objects and classes.
In Python, everything is an object. Classes define the structure and behavior of objects. Objects are just instances of classes.
In Python, we use the 'class' keyword to define classes and to create an object, we simply call the class name like a function. For example:
class Employee:
pass
emp = Employee()
Then there things called Attributes and Methods. Attributes are the variables that belong to a class while Methods are functions that also belong to class.
class Employee:
name = "John"
def __repr__(self):
print(f"Name: {self.name}")
emp = Employee()
print(emp)
Note: repr is a "magic method" that makes the display of the object instance look nicer. So the output in this case is:
Name: John
If you're curious, try remove the entire method for repr to check out the difference.
There is a way to have more control over the initialization of an object up creation and that is through the use of the init method. In programming lingo it's called a constructor method.
class Employee:
def init(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"{self.name}, {self.age} years old."
emp = Employee("John", 22)
print(emp)
When talking about Object Orientation, the word "Inheritance" get thrown around a lot. It simply means being able to define a class that inherits all the methods and properties from another class.
In the example that follows we will create a Manager class that inherits from the Employee class:
class Employee:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
# return f"Employee: {self.name}"
return f"{self.name}, {self.age} years old."
class Manager(Employee):
def __init__(self, name, age, employees=None):
super().__init__(name, age)
if employees is None:
self.employees = []
else:
self.employees = employees
def __repr__(self):
return f"Manager: {self.name}, {self.age} years old. Employees: {self.employees}"
john = Employee("John", 22)
print(john)
mary = Employee("Mary", 21)
print(mary)
mgr = Manager("Bob", 20, [john, mary])
print(mgr)
The built-in function super() has also been used here. It is used to call that parent class method (Employee class init constructor). It's commonly used in init methods to ensure that the child class properly initializes the parent class.
Another concept in Object-Oriented programming is Polymorphism. This refers to the ability to take multiple forms. Polymorphism allows us to define methods in the child class with the same name as in the parent class.
For example:
class Employee:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
# return f"Employee: {self.name}"
return f"{self.name}, {self.age} years old."
def do_work(self):
print(f"{self.name} is working employee stuff.")
class Manager(Employee):
def __init__(self, name, age, employees=None):
super().__init__(name, age)
if employees is None:
self.employees = []
else:
self.employees = employees
def __repr__(self):
return f"Manager: {self.name}, {self.age} years old. Employees: {self.employees}"
def do_work(self):
print(f"{self.name} is working manager stuff.")
john = Employee("John", 22)
print(john)
print(john.do_work())
mary = Employee("Mary", 21)
print(mary)
print(mary.do_work())
mgr = Manager("Bob", 20, [john, mary])
print(mgr)
print(mgr.do_work())
The next important concept to know is Encapsulation and it is merely the idea that an Instance of an object can wrap data and methods within it and that Python can put restrictions on accessing variables and methods directly.
We will modify the Employee class to demonstrate Encapsulation.
class Employee:
def __init__(self, name, age):
self.__name = name
self.__age = age
def __repr__(self):
# return f"Employee: {self.name}"
return f"{self.__name}, {self.__age} years old."
def do_work(self):
print(f"{self.__name} is working employee stuff.")
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def set_name(self, name):
self.__name = name
def set_age(self, age):
self.__age = age
class Manager(Employee):
def __init__(self, name, age, employees=None):
super().__init__(name, age)
if employees is None:
self.__employees = []
else:
self.__employees = employees
def __repr__(self):
return f"Manager: {self.get_name()}, {self.get_age()} years old. Employees: {self.__employees}"
def do_work(self):
print(f"{self.get_name()} is working manager stuff.")
john = Employee("John", 22)
print(john)
print(john.do_work())
mary = Employee("Mary", 21)
print(mary)
print(mary.do_work())
mgr = Manager("Bob", 20, [john, mary])
print(mgr)
print(mgr.do_work())
And finally, we have the concept of Abstraction which means making sure to provide only the necessary details and hiding the underlying implementation and this is achieved through the use of abstract classes and methods.
An abstract class is a class that has one or more abstract methods. An abstract method is a method that has a declaration but doesn't have an implementation.
Below, we introduce an abstract class called Worker that defins an abstract method called calculate_bonus. It is annotated by @abstractmethod. It can be noticed that the Employee class inherits from Worker and Manager indirectly inherits Worker indirectly through Employee and that they both provide an implementation of the the calculate_bonus method.
from abc import ABC, abstractmethod
class Worker:
@abstractmethod
def calculate_bonus(self):
pass
class Employee(Worker):
def __init__(self, name, age, salary):
self.__name = name
self.__age = age
self.__salary = salary
def __repr__(self):
# return f"Employee: {self.name}"
return f"{self.__name}, Bonus: {self.calculate_bonus()}, {self.__age} years old."
def do_work(self):
print(f"{self.__name} is working employee stuff.")
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def get_salary(self):
return self.__salary
def set_name(self, name):
self.__name = name
def set_age(self, age):
self.__age = age
def set_salary(self, salary):
self.__salary = salary
def calculate_bonus(self):
return self.__salary * 0.1
class Manager(Employee):
def __init__(self, name, age, salary, employees=None):
super().__init__(name, age, salary)
if employees is None:
self.__employees = []
else:
self.__employees = employees
def __repr__(self):
return f"Manager: {self.get_name()}, Bonus: {self.calculate_bonus()}, {self.get_age()} years old. Employees: {self.__employees}"
def do_work(self):
print(f"{self.get_name()} is working manager stuff.")
def calculate_bonus(self):
return self.get_salary() * 0.2
john = Employee("John", 22, 1000)
print(john)
print(john.do_work())
mary = Employee("Mary", 21, 1000)
print(mary)
print(mary.do_work())
mgr = Manager("Bob", 20, 2000, [john, mary])
print(mgr)
print(mgr.do_work())
OOP offers several benefits including modularity, code reusability which results in a clear structure for long and complex programs.
But although it offers many benefits, there may be situations where it may not be suitable and if it is not designed properly,
it cause programs to become over-complicated.
Posted on May 29, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.