Python OOP

Python and Python libraries use Object-Oriented Programming (OOP) everywhere. If you write Python code or are learning Python, you probably use OOP in your everyday work already.

2024-03-11 00:23:22 - ERneSt⚡️os

Object-oriented programming (OOP) is the Ikea of design paradigms. Hahaha. That’s ridiculous… but a blueprint is a blueprint. Ikea gets the job done. Anyone, anywhere there’s an Ikea can buy the same bookcase, put it together (meh), paint it and adorn it with anything they choose — books, plants, glass figurines. But it’s still the same bookcase. Or a school could buy it and put that exact same bookcase in each classroom with the exact number of books in the exact same order. If a photo of each bookcase was shown in a lineup, you wouldn’t be able to tell the difference. And of course everything in between. This is OOP. Each objects have the same fundamentals with unlimited capacity for customization.

The more software-ey way of introducing OOP: This architecture organizes code around objects. The objects include state that determines their characteristics and value properties. The objects perform behaviors that can change the object itself or the environment. OOP design principles aid in code modularity and reusability.

Python Object-Oriented Programming in the Wild

A picture is worth 1,000 words and we don’t have to look very far to find detailed pictures of OOP. Python and Python libraries use OOP everywhere. If you write Python code or are learning Python, you probably use OOP in your everyday work already.

If you type the following code in a Jupyter Notebook (install link):

my_list = []
print(type(my_list))
dir(my_list)

a list of all the available methods and dunder methods will appear. The dunder methods are denoted by the __. The dunder methods are special methods with predefined names that define how objects of a class behave in response to certain operations. The regular methods are user-defined functions within a class that are explicitly called. Both are passed into every list created in Python as a result of the Object-Oriented Programming paradigm.

This is true for all built-in data structures and you’ll find a similar result for dictionaries, sets, and others.


External Libraries

Have you ever worked with Python’s GUI library Tkinter or performed data analysis in Pandas? The import statement imports methods and dunder methods from those libraries. When you write code using those libraries you’re customizing your Ikea bookcase.

To see this in action, install Pandas on the Jupyter Notebook with !pip install pandas. Once that completes, running the following code will reveal all the methods and dunder methods that come with the Pandas import.


import pandas as pd
dir(pd)

The Basics

To successfully execute an application using the OOP architecture, you’ll need to build thoughtful, functional objects. All of those objects will start from a blueprint. They don’t all have to start with the same blueprint, but the point of OOP is that one blueprint will serve many different objects. Here is some basic information you’ll need to build any blueprint.

The number of attributes and methods are insignificant, the syntax is what matters in this example.


class Example:
    def __init__(self, attribute_one, attribute_two):
        self.attribute_one = attribute_one  
        self.attribute_two = attribute_two
 
    def method_one(self):
        if self.attribute_one > self.attribute_two:
            return True
        else:
            return False



To create an object from this class, which is called instantiate an instance of this class, we will do the following:

instantiated_object = Example(2, 8)

To reference the attributes we will do the following:

print(instantiated_object.attribute_one)
print(instantiated_object.attribute_two)

If we print instantiated_object.self we’ll get an error message.

We can run this whole thing back with:

instantiated_object_two = Example(197, 18630) or steve = Example(23, 78)We can keep repeating this over and over again. Of course, there are far more interesting objects. There are many instances of uniquely customized identical blueprints. Social media profiles (aka virtual Ikea if you will) are a good example, order systems are another. But not every class and instance thereof is going to look the same. Some blueprints are just a jumping-off point. For these cases, we have…


Parent and Child Classes

For this, let’s first think about an employee database. Some information is standard, everyone has a name, a department, and a salary. But not everyone has direct reports or knows a programming language. Here’s where parent and child classes come in handy. Child classes inherit all attributes of the parent class and can pass along their own attributes when instantiated.


class Employee:
    def __init__(self, name, salary, department):
        self.name = name
        self.salary = salary
        self.department = department
 
    def employee_info(self):
        return f"{self.name}, Salary: ${self.salary}, Department: {self.department}"

A parent class is still a class on its own. We can instantiate like we would any other class. The following code


jenn = Employee("Jenn", 100000, "Sales")
jenn.employee_info()

yields these results:

'Jenn, Salary: $100000, Department: Sales'

Now let’s talk managers. Managers are still employees but managers have direct reports and this company requires managers to attend quarterly manager trainings. The Managerclass will need to have specific criteria that don’t need to be included in every object. Since we don’t want to build all the Employee class attributes into a separate Manager class (because then we would have to do that for every iteration of this), we can create the Manager class as a child class with Employee as its parent. It will look like this:

class Manager(Employee):
    def __init__ (self, name, salary, department, direct_reports, trainings_attended):
        super().__init__(name, salary, department)
        self.direct_reports = direct_reports
        self.trainings_attended = trainings_attended
 
    def add_direct_reports(self):
        self.direct_reports += 1
 
    def subtract_direct_reports(self):
        self.direct_reports -= 1
    
    def add_trainings_attended(self):
        self.trainings_attended += 1
 
    def manager_info(self):
        return f"Manager: {super().employee_info()}, Direct Reports: {self.direct_reports}, Trainings Attended: {self.trainings_attended}"

You will instantiate a child class in the same way you do any other class. lisa = Manager(“Lisa”, 100000, “content”, 3, 4)`.

One thing you’ll notice about this child class we still have attributes from the employee class written out the __init__ line before we added the new manager class-specific attributes such as direct_reports’and trainings_attended. You’ll also see directly in the next line Python uses super().__init__(). super().__init__()essentially points your code to the initialization logic defined in the parent class, which in this case is the Employee class. So even though we have to define this code at the top of the object, we don’t have to go through the work of also including self.name = name in the child class.

You’ll also notice that we have four methods, two related to adding and subtracting direct reports, one for adding trainings, and one to return all manager information.


lisa.manager_info()

yields the following information:

‘Manager: Lisa, Salary: $100000, Department: content, Direct Reports: 3, Trainings Attended: 4’

We can call the following any time Lisa hires someone new.

lisa.add_direct_reports()

lisa.direct_reports = 4 will also have the same effect.

Developers also work at this company. This company needs a different set of identification details for developers. For that reason, we can build another child class from the same parent Employee class.


class Developer(Employee):
    def __init__ (self, name, employee_id, salary, department, languages):
        super().__init__(name, employee_id, salary, department)
        self.languages = languages
 
    def add_language(self, new_language):
        self.languages.append(new_language)
 
    def employee_info(self):
        return f"Manager: {super().display_info()}, Languages: {', '.join(self.languages)}"


We can instantiate and check out our new object.




More Posts