The Pillars of OOP and How they Work in Python: Part 2 Introduction to classes and objects

winstonmhango23

8 min read

Views: 199

Welcome to the second part of our series on Object-Oriented Programming (OOP) in Python. In the first part, we laid the foundation by explaining what objects are, what OOP is, and how to use Python's built-in data structures to organize data.

Recap of Fundamental Concepts

Variables, Lists, Tuples, Dictionaries, and Arrays

In the previous part, we discussed how variables store data, lists and tuples group related items, dictionaries store key-value pairs, and arrays efficiently handle numerical operations. These structures are the building blocks for more complex data representations.

Modeling Objects Without OOP

We also demonstrated how to model a bank account using dictionaries and lists, showcasing the limitations of this approach as complexity increases. Now, let's transition to using OOP principles to create more structured and maintainable code.

Transition to OOP: Introduction to Classes and Objects

What is a Class?

A class is a blueprint for creating objects. It defines a set of attributes and methods that the objects created from the class can use. Think of a class as a template for creating specific instances of an object.

What is an Object?

An object is an instance of a class. It contains data (attributes) and functions (methods) defined by its class. Each object created from a class can have its own unique values for the attributes.

Example: Defining a Simple Class

class BankAccount:
    def __init__(self, account_number, name, balance=0):
        self.account_number = account_number
        self.name = name
        self.balance = balance
        self.transactions = []

    def deposit(self, amount):
        self.balance += amount
        self.transactions.append(amount)

    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
            self.transactions.append(-amount)
        else:
            print("Insufficient funds")

    def get_balance(self):
        return self.balance

    def get_transactions(self):
        return self.transactions

Core Principles of OOP

1. Encapsulation

Encapsulation is the concept of wrapping data (variables) and methods (functions) that operate on the data into a single unit, or class. This hides the internal state of the object and only exposes the necessary methods to interact with that object. It ensures better control over the data and prevents external interference and misuse.

Python Implementation

In Python, you can define private attributes by prefixing the attribute name with two underscores (__). Private attributes cannot be accessed directly from outside the class; they can only be accessed through methods defined within the class.

class BankAccount:
    def __init__(self, account_number, name, balance=0):
        self.__account_number = account_number  # Private attribute
        self.__balance = balance  # Private attribute
        self.__transactions = []

    def deposit(self, amount):
        self.__balance += amount
        self.__transactions.append(amount)

    def withdraw(self, amount):
        if amount <= self.__balance:
            self.__balance -= amount
            self.__transactions.append(-amount)
        else:
            print("Insufficient funds")

    def get_balance(self):
        return self.__balance

    def get_transactions(self):
        return self.__transactions

Java Implementation

In Java, you use access modifiers like private to encapsulate data. Getter and setter methods are used to access and modify private attributes.

public class BankAccount {
    private String accountNumber;
    private double balance;

    public BankAccount(String accountNumber, double balance) {
        this.accountNumber = accountNumber;
        this.balance = balance;
    }

    public void deposit(double amount) {
        this.balance += amount;
    }

    public void withdraw(double amount) {
        if (amount <= this.balance) {
            this.balance -= amount;
        } else {
            System.out.println("Insufficient funds");
        }
    }

    public double getBalance() {
        return this.balance;
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount("001", 1000);
        account.deposit(500);
        System.out.println(account.getBalance());  // Output: 1500
        account.withdraw(200);
        System.out.println(account.getBalance());  // Output: 1300
    }
}

2. Inheritance

Inheritance is a mechanism where a new class (child class) inherits properties and behaviors (methods) from an existing class (parent class). This promotes code reuse and establishes a relationship between the parent and child classes.

Python Implementation

To create a child class that inherits from a parent class, you simply pass the parent class as an argument to the child class definition.

class BankAccount:
    def __init__(self, account_number, name, balance=0):
        self.account_number = account_number
        self.name = name
        self.balance = balance
        self.transactions = []

    def deposit(self, amount):
        self.balance += amount
        self.transactions.append(amount)

    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
            self.transactions.append(-amount)
        else:
            print("Insufficient funds")

class SavingsAccount(BankAccount):
    def calculate_interest(self):
        return self.balance * 0.05

class CheckingAccount(BankAccount):
    def deduct_fees(self):
        self.balance -= 10

savings = SavingsAccount("001", "John Doe", 1000)
print(savings.calculate_interest())  # Output: 50.0

checking = CheckingAccount("002", "Jane Doe", 1000)
checking.deduct_fees()
print(checking.balance)  # Output: 990

Java Implementation

In Java, the extends keyword is used to create a subclass that inherits from a superclass.

public class BankAccount {
    protected String accountNumber;
    protected double balance;

    public BankAccount(String accountNumber, double balance) {
        this.accountNumber = accountNumber;
        this.balance = balance;
    }

    public void deposit(double amount) {
        this.balance += amount;
    }

    public void withdraw(double amount) {
        if (amount <= this.balance) {
            this.balance -= amount;
        } else {
            System.out.println("Insufficient funds");
        }
    }
}

public class SavingsAccount extends BankAccount {
    public SavingsAccount(String accountNumber, double balance) {
        super(accountNumber, balance);
    }

    public double calculateInterest() {
        return this.balance * 0.05;
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        SavingsAccount savings = new SavingsAccount("001", 1000);
        System.out.println(savings.calculateInterest());  // Output: 50.0
        savings.deposit(500);
        System.out.println(savings.balance);  // Output: 1500
    }
}

3. Abstraction

Abstraction hides the internal implementation details and exposes only the necessary functionality. It can be achieved using abstract classes and methods.

from abc import ABC, abstractmethod

class Account(ABC):
    @abstractmethod
    def calculate_interest(self):
        pass

class SavingsAccount(Account):
    def __init__(self, balance):
        self.balance = balance

    def calculate_interest(self):
        return self.balance * 0.05

savings = SavingsAccount(1000)
print(savings.calculate_interest())  # Output: 50.0

4. Polymorphism

Polymorphism allows objects of different classes to be treated as objects of a common superclass. It provides a way to perform a single action in different forms.

Python Implementation

Polymorphism is typically implemented using method overriding, where a child class provides a specific implementation of a method that is already defined in its parent class.

class BankAccount:
    def account_type(self):
        pass

class SavingsAccount(BankAccount):
    def account_type(self):
        return "Savings Account"

class CheckingAccount(BankAccount):
    def account_type(self):
        return "Checking Account"

def print_account_type(account):
    print(account.account_type())

savings = SavingsAccount()
checking = CheckingAccount()

print_account_type(savings)   # Output: Savings Account
print_account_type(checking)  # Output: Checking Account

Java Implementation

In Java, polymorphism is achieved through method overriding and the use of the @Override annotation.

public class BankAccount {
    public String accountType() {
        return "Generic Account";
    }
}

public class SavingsAccount extends BankAccount {
    @Override
    public String accountType() {
        return "Savings Account";
    }
}

public class CheckingAccount extends BankAccount {
    @Override
    public String accountType() {
        return "Checking Account";
    }
}

public class Main {
    public static void printAccountType(BankAccount account) {
        System.out.println(account.accountType());
    }

    public static void main(String[] args) {
        SavingsAccount savings = new SavingsAccount();
        CheckingAccount checking = new CheckingAccount();

        printAccountType(savings);   // Output: Savings Account
        printAccountType(checking);  // Output: Checking Account
    }
}

Conclusion

In this second part of our series, we focused on the basic principles of OOP: encapsulation, inheritance, abstraction, and polymorphism. We explored how these principles differ from procedural programming and the syntax rules involved in their implementation. Additionally, we compared the implementation of these principles in Python with Java to highlight the differences in syntax and approach. Understanding these principles is crucial for creating structured, maintainable, and reusable code in Python and also, if you are from a Java, c++, or PHP background, the Java code provides a fallback comparison to what you may already be familiar with.

In the next part, we will delve into more advanced OOP concepts, including composition, aggregation, association, and the Method Resolution Order (MRO). Stay tuned!

Stay tuned for more advanced topics and practical examples in the next part of our series!

Recent Related Posts

Related Posts

Creating and uploading a Python package to the PyPi: Part 3 Creating The package and uploading to pypi.org

In this part of the series, we will walk you through the final steps of creating a Python package and uploading it to PyPi. We will use the example package bank_creator that we discussed in Part 2. The full code for this package can be found on GitHub.

Read More

Creating and uploading a Python package to the PyPi: Part 2 Creating Git repository and uploading to github

Read More

Creating and uploading a Python package to the PyPi: Part 1 Basics

Read More

© 2024 .