Home
About Us Privacy Policy
 

OBJECT ORIENTED PROGRAMMING IN PYTHON

ADVERTISEMENT

Advanced Classes



A brief description of Design Structure and its importance in software engineering?

Design Structure is the philosophy of creating a structure following the best design elements working cohesively to create a structurally sound system. This philosophy is borrowed from engineering disciples.

We all have witnessed numerous examples of structurally unstable bridges that seem to collapse during development or after launch. One of the primary reasons behind bridge collapse is design and structural deficiencies. On the other hand, well-constructed structures such as the Wall of Japan to protect from tsunamis demonstrate the gravity of following sound design principles.

Software engineering is considered to be a branch of engineering. Hence, thinking about structures and elements helps us engineer a cohesive product with all its components working together, resulting in a stable and robust end-product. Therefore, following a proper design structure is integral for the development of the highest quality of software.


What Is Object-Oriented Programming?

Object-Oriented Programming or (OOP) for short, is a programming paradigm that follows the concept of objects. An object can store both the data and methods that can act upon it. For better understanding, let us take an example of a motorcycle.

In a motorcycle, there are different components such as:

  • fuel tank
  • no of gears
  • switch gears
  • wheel size
  • top speed
  • fuel efficiency
  • operator accelerator
  • operate brake
  • clutch
  • gauge
  • headlight type
  • toggle headlight
  • toggle engine
  • toggle ignition

There are, of course, a lot more components; I've mentioned just a few.

Let us segregate methods and attributes from the above information.

Attributes

  • fuel tank
  • wheel size
  • top speed
  • fuel efficiency
  • gauge
  • headlight type
  • no of gears

Methods

  • toggle ignition
  • operate the clutch
  • operate brake
  • use the accelerator
  • switch gears
  • toggle headlight
  • toggle engine

In this example, the bike consists of various data points, such as the top speed and the accelerator, which we can use to reach our desired velocity. The top speed is our data, and using the accelerator is our method.

As the above example demonstrates, Object-Oriented programming allows for modeling our application based on real-world objects. It enables the developers to provide an abstraction to develop well-maintainable, robust software.


What is a Class, and why are they used?

In Python, classes can be thought of as templates. Classes provide all the capabilities of the Object-Oriented programming paradigm, which stores both the code(methods) and the data(attributes/property). They allow for defining the methods(actions that can be performed) and the data(on whom the methods will operate on).

They are used to allow the programmer to develop the application using the Object-Oriented Programming Paradigm, resulting in a maintainable, small, clear, and concise codebase.


Advantages of OOP

  • OOP allows for the development of reusable components, reducing development resources.
  • They allow for classes to inherit properties of different classes using an in-built feature inheritance.
  • OOP allows for access privileges of methods and attributes/properties using the public, private modifiers. By default, every method/attribute/property is public.


Difference between Class and Non-Class Implementation.

Let us write a simple program to display vehicle information, demonstrating the difference between Class and Non-Class Implementation.

We will be creating three vehicles named Ford Fiesta, Toyota Corolla, Honda Civic and store the following information and methods:

  • Vehicle Brand
  • Vehicle Type
  • Top Speed
  • Is Registered
  • Owner Name
  • Model
  • Color
  • Is Insured

Non-Class Implementation

# Ford Fiesta
ford_fiesta_brand = "Ford"
ford_fiesta_type = "Car"
ford_fiesta_top_speed = 150
ford_fiesta_is_registered = False
ford_fiesta_owner_name = "Mr. Anderson"
ford_fiesta_model = "Fiesta"
ford_fiesta_color = "Yellow"
ford_fiesta_is_insured = True 

def get_ford_fiesta_name():
    return ford_fiesta_brand + " " + ford_fiesta_model

def get_ford_fiesta_vehicle_type():
    return ford_fiesta_type

def get_ford_fiesta_top_speed():
    return ford_fiesta_top_speed

def is_ford_fiesta_registered():
    return ford_fiesta_is_registered

def get_owner_of_ford_fiesta():
    return ford_fiesta_owner_name

def get_ford_fiesta_color():
    return ford_fiesta_color

def is_ford_fiesta_insured():
    return ford_fiesta_is_insured

# Toyota Corolla
toyota_corolla_brand = "Toyota"
toyota_corolla_type = "Car"
toyota_corolla_top_speed = 180
toyota_corolla_is_registered = True
toyota_corolla_owner_name = "Mr. Neo"
toyota_corolla_model = "Corolla"
toyota_corolla_color = "Black"
toyota_corolla_is_insured = True 

def get_toyota_corolla_name():
    return toyota_corolla_brand + " " + toyota_corolla_model

def get_toyota_corolla_vehicle_type():
    return toyota_corolla_type

def get_toyota_corolla_top_speed():
    return toyota_corolla_top_speed

def is_toyota_corolla_registered():
    return toyota_corolla_is_registered

def get_owner_of_ford_fiesta():
    return toyota_corolla_owner_name

def get_toyota_corolla_color():
    return toyota_corolla_color

def is_toyota_corolla_insured():
    return toyota_corolla_is_insured

# Honda Civic
honda_civic_brand = "Honda"
honda_civic_type = "Car"
honda_civic_top_speed = 140
honda_civic_is_registered = True
honda_civic_owner_name = "Mr. Jill"
honda_civic_model = "Civic"
honda_civic_color = "White"
honda_civic_is_insured = True 

def get_honda_civic_name():
    return honda_civic_brand + " " + honda_civic_model

def get_honda_civic_vehicle_type():
    return honda_civic_type

def get_honda_civic_top_speed():
    return honda_civic_top_speed

def is_honda_civic_registered():
    return honda_civic_is_registered

def get_owner_of_ford_fiesta():
    return honda_civic_owner_name

def get_honda_civic_color():
    return honda_civic_color

def is_honda_civic_insured():
    return honda_civic_is_insured

Class Implementation

class Vehicle:

    def __init__(self, brand, type, top_speed, is_registered, owner_name, model, color, is_insured):
        self.brand = brand
        self.type = type
        self.top_speed = top_speed
        self.is_registered = is_registered
        self.owner_name = owner_name
        self.model = mode
        self.color = color
        self.is_insured = is_insured

    def get_name(self):
        return f"{self.brand} {self.model}"

    def get_type(self):
        return self.type

    def get_top_speed(self):
        return self.top_speed

    def is_registered(self):
        return self.is_registered

    def get_owner_name(self):
        return self.owner_name

    def get_color(self):
        return self.color

    def is_insured(self):
        return self.insured

ford_fiesta = Vehicle("Ford", "Car", 150, False, "Mr. Anderson", "Fiesta", "Yellow", True)
toyota_corolla = Vehicle("Toyota", "Car", 180, True, "Mr. Neo", "Corolla", "Black", True)
honda_civic = Vehicle("Honda", "Car", 140, True, "Mr Jill", "Civic", "White", True)

The above two examples perfectly demonstrate the power of object-oriented programming by providing very high levels of abstraction, resulting in a cleaner, manageable codebase.

The non-class implementation has many redundant statements, is challenging to maintain and understand, and spans 89 lines. It would be almost impossible to scale to hundreds or even thousands of vehicles without causing management and documentation issues.

However, the class-based implementation is concise, shorter, easier to understand and maintain, omits redundancy, and spans only 36 lines. It is also shorter by 60%.

The following section will discuss using the class keyword to declare a class in Python.


ADVERTISEMENT

How to declare a class?

In Python, you can declare a class using the class keyword, followed by the class name. Below is an example.

class MyClass:
    statement 1
    statement 2
    statement 3
    statement ...
    statement ...
    statement N


What is the naming convention for declaring Class Names?

According to Python's PEP-8(Style Guide for Python), you must start each word in the class name as a Capital letter and not use separate words with an underscore. This style is also known as the camel case.

Here are some examples of correct naming conventions:

#1:
class MyFavouriteClass:
    pass

#2:
class Model:
    pass

#3:
class View:
    pass

#4:
class Encryption:
    pass

#5:
class Class128:
    pass

Here are some examples of incorrect naming conventions or invalid syntax:

#1:
class My_Favourite_class:
    pass

#2:
class model:
    pass

#3:
class 1:
    pass

#4:
class class_0:
    pass


Members of a Class

As stated, Object-Oriented Principles allow for storing both the methods(actions) and properties(data). You can define the following inside of a class:

  • instance members
    • instance variables
    • instance methods
  • class members
    • class variables
    • class methods


Instance Members

Instance members are members that belong to a particular copy or instance of a class. Below is an example to help understand them:

class Person:
    def __init__(self, name, age):

        # Instance Variables
        self.name = name
        self.age = age
        
    # Instance method
    def get_info(self):
        return f"| {self.name}, {self.age} |\n"

# Neo is an instance of the class Person
neo = Person("Mr. Neo", 40)

# Anderson is an instance of the class Person
anderson = Person("Mr. Anderson", 50)

# Neo Info
neo_info = neo.get_info()
print(neo_info)

# Anderson info
anderson_info = anderson.get_info()
print(anderson_info)

| Mr. Neo, 40 |

| Mr. Anderson, 50 |

Let us go through every statement and understand the structure.

#1:

class Person:

This statement informs the Python interpreter that a class is to be declared.

#2:

def __init__(self, name, age):

This statement defines an instance method of the class Person that takes three arguments:

  • self: Reference to the current instance. This argument should be the first argument and is implicitly passed by Python.
  • name: This argument will accept our name.
  • age: This argument will accept our age
The __init__ method is a constructor of class Person. Constructors will be covered later.

#3:

self.name = name
self.age = age

These statements define the instance variable's name and age and assign the value passed using the method parameters name and age. It cannot be apparent to understand initially. But, please follow through.

In Python, we declare instance variables by using the self.<instance_variable_name> notation.

Therefore, to declare any instance variable, we have to refer to it using self.

#4:

def get_info(self):
    return f"| {self.name}, {self.age} |\n"

These statements define the instance method get_info(). To declare an instance method, one must always pass self as the first argument. This function returns a formatted string containing the name and the age.

#5:

neo = Person("Mr. Neo", 40)

This statement defines neo as an instance of the class Person. Now, the values passed refer to the instance method __init__. As you can see below, __init__ method accepts name and age.

def __init__(self, name, age)

Python implicitly passes the self argument. Hence, when

Person("Mr. Neo", 40)
is called, __init__ method executes with "Mr. Neo" as the value to name and 40 as the value to age. The same is with
anderson = Person("Mr. Anderson", 50)

#6:

neo_info = neo.get_info()

This method calls the instance method get_info that returns the name and age of the instance neo.

Here name and age are instance variables, and get_info is the instance method .

To define an instance variable, refer to them using self. Here is an example:

self.bank_balance = 1_000_000

To define an instance method, always accept the first argument self. Here is an example:

def get_bank_balance(self):
    return self.bank_balance


ADVERTISEMENT

What are class members?

Class Members are like instance members, except they belong to the entire class. Below is an example:

class Constants:

    pi = 3.14
    speed_of_light = 299_792_458

    @classmethod
    def get_pi(cls):
        return cls.pi

    @classmethod
    def get_speed_of_light(cls):
        return cls.speed_of_light

In the above example:

#1:

class Constants:

This statement informs Python that a class is being declared named Constants.

#2:

pi = 3.14
speed_of_light = 299_792_458

These statements declare the class variables pi and speed_of_light.

#3:

@classmethod
def get_pi(cls):
    return cls.pi

@classmethod is a decorator. Decorators are used to pass additional information or metadata to Python. To inform Python that get_pi is a class method, we have to use the @classmethod decorator.

The get_pi method accepts a reference to the class itself using the cls parameter. It should always be the first argument and must always be defined as cls.

Finally, this method returns the value of the class variable pi using cls.<class_variable> notation.


@staticmethod

We have discussed instance and class methods. However, Python allows for an additional method type: static method.

Static methods are methods that cannot have access to instance variables. However, it allows for indirect access to class members using the class name instead of cls. Below is an example:

class PI:
    value = 3.14

    @staticmethod
    def get_value():
        return PI.value

print(PI.get_value())

3.14


Naming convention for members

According to PEP-8(Style Guide for Python), you must define all members in lowercase, with each word separated by an underscore. It applies to both variables and methods.

Below is an example of the correct convention:

name, my_bank_balance, my_age, pi, get_pi, get_name, get_bank_balance


Deleting members

Since Python is an interpreted language, it allows for runtime modification of code. One of the things Python allows is deleting members using the del keyword.

To delete a member, use the following syntax:

del <class_name>.<member_name>

Below is an example:

class Person:
    def __init__(self, name):
        self.name = name

    @staticmethod
    def msg():
        print("Hello, from class Person")
    
    def get_name(self):
        return self.name

Person.msg()
del Person.msg
try:
    Person.msg()
except AttributeError as e:
    print(e)
    
me = Person("Mr. X")
print(me.name)
del me.name
try:
    print(me.name)
except AttributeError as e:
    print(e)

Hello, from class Person
type object 'Person' has no attribute 'msg'
Mr. X
'Person' object has no attribute 'name'


Access modifiers

The Object-Oriented Programming Paradigm allows for limiting the accessibility of members. Python provides two access modifiers:

  • public
  • private


Public access Modifier

Public access means that the members are available outside the class they are part of. Below is an example of a public member.

class Msg:
    def __init__(self):
        self.msg = "Hello, World"
    
    @staticmethod
    def say_bye():
        print("Bye")

my_msg= Msg()
print(my_msg.msg)
Msg.say_bye()

Hello, World
Bye

The member say_bye and msg are public members.

By default, all members are public.


Private access modifiers

Private members are "visible" inside the class that defines them. Private members must begin with a double underscore(__).

Below is an example:

class Msg:
    def __init__(self):
        self.msg = "Hello, World"
        self.__my_secret_msg()

    @staticmethod
    def say_bye():
        print("Bye")

    def __my_secret_msg(self):
        print("Secret message from Msg")

my_msg = Msg()

Secret message from Msg


Difference between public and private access modifier.

In the previous section, we learned about the public and private access modifiers. However, in this section, we will discuss their difference in-depth as well as discuss their purpose.

The public access modifier is used when we want a method or a property/attribute to be accessible from outside the scope of the class. It is the default mode.

The private access modifier is used to protect the method/property/attribute from being accessible from outside the class's scope. The marked methods/property/attribute is only available from inside the scope of the class it is defined within.


__init__ method and Default Values in Constructors

We looked at the __init__ method to initialize out instances in the earlier section. Let us take an example to help understand constructors:

class Person:
    def __init__(self, name, age, address, mobile_no, sin_no):
        self.name = name
        self.age = age
        self.address = address
        self.mobile_no = mobile_no
        self.sin_no = sin_no
                
    def get_info(self):
        return f"{self.name}, {self.age}, {self.address}, {self.mobile_no}, {self.sin_no}"

me = Person("Mr. Bean", 50, "123 Lost Street", '+16578767651', '786-897-657')
print(me.get_info())

In the above example, during the instantiation, we are accepting the following:

  • name
  • age
  • address
  • mobile_no
  • sin_no

However, what if we wanted to create a record for a new person in the country who does not have an address or sin_no. We would not be able to as address and sin_no are mandatory arguments and do not have a default value. To address such instances, Python allows for providing default values. Hence, we would define a constructor as such:

def __init__(self, name, age, mobile_no, address = "N/A", sin_no="N/A"):
    self.name = name
    self.age = age
    self.mobile_no = mobile_no
    self.address = address
    self.sin_no = sin_no

Complete Example

class Person:

    def __init__(self, name, age, mobile_no, address = "N/A", sin_no="N/A"):
        self.name = name
        self.age = age
        self.mobile_no = mobile_no
        self.address = address
        self.sin_no = sin_no

    def get_info(self):
        return f"{self.name}, {self.age}, {self.address}, {self.mobile_no}, {self.sin_no}"

bean = Person("Mr. Bean", 50, "123 Lost Street", '+16578767651', '786-897-657')
new_person = Person("Mr. Johnson", 45, "Bulk Street")
print(bean.get_info())
print(new_person.get_info())

Mr. Bean, 50, +16578767651, 123 Lost Street, 786-897-657
Mr. Johnson, 45, N/A, Bulk Street, N/A


ADVERTISEMENT

Inheritance

In the above sections, we have observed the power of abstraction that the Object-Oriented Programming paradigm(OOP) provides. It allows for re-using of code and achieving maintainable codebases. One of the highlights of OOP is that it allows for deriving of features and properties of one class into another. One of the most remarkable real-world examples is Evolution. It allows for the borrowing of elements from different categories of organisms.

In the above illustration, the Dog and the Cat borrow a standard set of features from the class Animal. It allows for inheriting all the features of the class Animal into the class Dog and Cat. This feature is called inheritance. In Python, you can only inherit a single class at once. However, subsequent inheritance of classes is allowed.

The class inheriting another class is referred to as the child class. The inherited class is referred to as the parent class.


How to inherit a class?

To inherit a class, use the following syntax:

class ChildClass(<parent_class_name>):
    statement 1
    statement 2
    statement 3
    statement ...
    statement ...
    statement N

Below is an example:

class Animal:
    def __init__(self, name, animal_type, no_of_legs, have_tail, favourite_food):
        self.name = name
        self.animal_type = animal_type
        self.no_of_legs = no_of_legs
        self.have_tail = have_tail
        self.favourite_food = favourite_food
        
    def get_name(self):
        return self.name 

    def get_type(self):
        return self.type

    def get_no_of_legs(self):
        return self.no_of_legs

    def have_tail(self):
        return self.have_tail

    def eat(self):
        print(f"Eating my {self.favourite_food}!")

class Dog(Animal):
    def __init__(self, name, animal_type, no_of_legs, have_tail, favourite_food):
        super().__init__(name, animal_type, no_of_legs, have_tail, favourite_food)

    def bark(self):
        print("Woof Woof!")

    def wag_tail(self):
        print("Wagging my tail!")

class Cat(Animal):
    def __init__(self, name, animal_type, no_of_legs, have_tail, favourite_food):
        super().__init__(name, animal_type, no_of_legs, have_tail, favourite_food)

    def meow(self):
        print("Meow!")
        
    def slap(self, msg=""):
        print(f"{msg.strip()} I'm going to slap you with my paws!")

bruno = Dog("Bruno", "Dog", 4, True, "Cookies")
tom = Cat("Tom", "Cat", 4, True, "Fish")

bruno.eat()
tom.eat()

bruno.bark()
tom.slap("If you bark again,")

Eating my Cookies!
Eating my Fish!
Woof Woof!
If you bark again, I'm going to slap you with my paws!

Let us break down each statement to help understand the anatomy of this program.

#1:

class Animal:

This statement informs Python that we are declaring a class named "Animal".

#2:

def __init__(self, name, animal_type, no_of_legs, have_tail, favourite_food):
    self.name = name
    self.animal_type = animal_type
    self.no_of_legs = no_of_legs
    self.have_tail = have_tail
    self.favourite_food = favourite_food

This method initializes the class/instance variables.

#3:

def get_name(self):
    return self.name

This method defines the instance method get_name that returns the name of the animal.

#4:

def get_type(self):
    return self.type

This method defines the instance method get_type that returns the type of the animal.

#5:

def get_no_of_legs(self):
    return self.no_of_legs

This method defines the instance method get_no_of_legs that returns the no. of legs the animal has.

#6:

def have_tail(self):
    return self.have_tail

This method defines the instance method that returns a boolean value whether the animal has a tail or not.

#7:

def eat(self):
    print(f"Eating my {self.favourite_food}!")

This method defines an instance method that prints a message along with the animal's favorite food.

#8:

class Dog(Animal):

This statement informs Python that we are declaring a class named Dog that will inherit all of the methods(code) and properties(data) of the parent class "Animal".

#9:

def __init__(self, name, animal_type, no_of_legs, have_tail, favourite_food):
    super().__init__(name, animal_type, no_of_legs, have_tail, favourite_food)

This method initializes the Dog instance. It calls the __init__ method from the parent class using the super keyword. It tells Python to refer to the parent class, call its __init__ method, and pass the relevant arguments.

#10:

def bark(self):
    print("Woof Woof!")

This method defines the instance method bark. This method will only be available to the Dog class.

#11:

def wag_tail(self):
    print("Wagging my tail!")

This method defines the instance method wag_tail. This method will only be available to the Dog class.

#12:

class Cat(Animal):

This statement informs Python that we are declaring a class named Cat that will inherit all of the methods(code) and properties(data) of the parent class "Animal".

#13:

def __init__(self, name, animal_type, no_of_legs, have_tail, favourite_food):
    super().__init__(name, animal_type, no_of_legs, have_tail, favourite_food)

This method initializes the Cat instance. It calls the __init__ method from the parent class using the super keyword. It tells Python to refer to the parent class, call its __init__ method, and pass the relevant arguments.

#14:

def meow(self):
    print("Meow!")

This method defines the instance method meow. This method will only be available to the Cat class.

#15:

def slap(self, msg=""):
    print(f"{msg.strip()} I'm going to slap you with my paws!")

This method defines the instance method slap. This method will only be available to the Cat class.

#16:

bruno = Dog("Bruno", "Dog", 4, True, "Cookies")
tom = Cat("Tom", "Cat", 4, True, "Fish")

These statements create the instance of Dog and Cat class and pass relevant values.

#17:

bruno.eat()
tom.eat()

These statements call the eat method for both bruno and tom instances.

#18:

bruno.bark()

This method calls the bark method of the dog class for bruno instance.

#19:

tom.slap("If you bark again,")

This method calls the slap method of the Cat class, for instance, tom.


super keyword

We have learned that the object-oriented paradigm allows for inheriting properties and methods from another class. However, there are certain instances where we might need to refer to the parent class and access its value. To refer to the parent class, Python provides the super keyword.
Below are some examples of using the super keyword:

To refer to the superclass method:

class ParentClass:
    def __init__(self):
        self.secret_value = 8
                
    def print_secret_value(self):
        print(self.secret_value)

class ChildClass(ParentClass):
    def __init__(self):
        super().__init__()
        super().print_secret_value()

temp = ChildClass()

8

One thing to always remember is that super can only refer to instance members of the parent class.


Conclusion

In this chapter, we learned about the significance of real-world modeling of problems, object-oriented programming paradigm, the difference between class and non-class implementation, naming conventions, instance members, class members, class members, static method, deletion of members, access modifiers, and inheritance. OOP will help you write large, scalable, and maintainable software effortlessly.


ADVERTISEMENT



All product names, logos, and brands are property of their respective owners.