Skip to main content

Command Palette

Search for a command to run...

Object Oriented Programming in Python

Published
13 min read
Object Oriented Programming in Python
K

🌟 Background & Passion:

With a background in Data Science and Natural Language Processing, I've always been fascinated by the power of programming to solve real-world problems. My journey into the world of Python began in college days when I first took a course in Python. Ever since, I've been passionately exploring the endless possibilities that Python offers.

🔍 What I Do:

By day, I'm a freelance Data Scientist. By night, I turn into a Python explorer, delving into new libraries, and frameworks, and constantly updating my blog to share my learnings and experiences.

✍️ My Blog's Mission:

Code and Query is more than just a blog; it's a platform where I aim to simplify Python programming and Data Science/Machine Learning concepts for beginners and enthusiasts alike. From basic concepts to advanced techniques, I strive to make my posts as clear, comprehensive, and engaging as possible. My goal is to help you not just understand data, but also appreciate its elegance and efficiency and derive trends and insights.

🌐 Beyond Python:

When I'm not coding or writing, you'll find me writing poetries or reading philosophy. I believe in a balanced life, where passions outside of work fuel creativity and new ideas within my professional sphere.

💬 Let's Connect:

I love connecting with fellow Python enthusiasts and tech lovers. Feel free to reach out to me on kritishapanda75@gmail.com or other social media handles on my profile. Whether it’s feedback, ideas, or just a chat about technology, I'm all ears!

Success is not final, failure is not fatal: It is the courage to continue that counts.

Let's start with a new concept today...

Understanding Object-Oriented Programming (OOP) Through an Easy Analogy

Object-Oriented Programming (OOP) is a programming paradigm centered around the concept of "objects." These objects can be thought of as entities that combine both data and the operations that can be performed on that data. To understand this better, let's consider an analogy from the manufacturing world: the assembly line.

In an assembly line, raw materials (akin to data in programming) are processed and transformed into finished products. The machinery and workers on the line represent the system components, similar to the methods and functions in OOP that act upon data. Just as each worker or machine on an assembly line performs a specific task on the raw materials to gradually create a finished product, in OOP, methods (functions within a class) operate on data (attributes) to achieve complex operations.

In traditional programming paradigms, like procedural programming, data and procedures are separate. This is akin to having a warehouse full of parts (data) and a set of separate tools (procedures) with no inherent relationship between them. While this approach works well for simpler, linear tasks, it becomes cumbersome and inefficient when dealing with complex, real-world problems.

Real-world scenarios are often too complex to be effectively modeled with just standalone data types and procedures. For example, consider modeling a vehicle in a computer program. A vehicle isn't just a list of parts (data); it's a system where each part interacts with others in specific ways. OOP allows us to model these complex entities more naturally by encapsulating the relevant data (like engine power, fuel type, color) and behaviors (like start, stop, accelerate) into a single unit, a class named Vehicle. This encapsulation makes the code more intuitive, reusable, and scalable, much like how an assembly line allows for efficient production and modification of complex products.

Objects are at the center of object-oriented programming in Python. In other programming paradigms, objects only represent the data. In OOP, they additionally inform the overall structure of the program.

Classes vs. Instances: Understanding the Difference

The distinction between classes and instances is fundamental in Object-Oriented Programming. Understanding this distinction is crucial for effectively using OOP concepts in Python or any other object-oriented language. To clarify this, let's use the analogy of a form or questionnaire.

Classes: The Blueprint

A class in OOP can be thought of as a form or questionnaire. It's a blueprint or a template that defines the structure and capabilities of what you want to create. This template specifies what information (attributes) and actions (methods) are applicable for the object you're modeling.

For example, consider a class Employee. This class serves as a blueprint for what constitutes an employee. It will have attributes like name, employee_id, position, and methods like update_salary() or display_information(). However, at this stage, the class is just a template with no specific information about any individual employee.

Instances: The Filled Forms

An instance is what you get when you fill out a form with specific information. It's a concrete manifestation of the class. When you create an instance of a class, you’re essentially filling out the blueprint with actual, specific details.

Going back to the Employee class example, when you create an instance of this class, you're creating a specific employee. For instance, an employee named Alice with an employee_id of 123, holding the position of 'Engineer'. This instance holds actual data that pertains to Alice, distinct from any other employee.

Just as many people can fill out the same form with their unique information, you can create many instances from a single class. Each instance represents a separate, distinct entity with its own set of attributes based on the same blueprint (class). So, while the class defines what information and actions are possible, the instance embodies the specific details and state.

Class Definition and Indentation

In Python, defining a class involves using the class keyword, followed by the class name and a colon. The body of the class is then indented. This indentation is crucial as Python uses whitespace to define scope.

Here's an example of a basic class definition:

class Dog:
    # Class body goes here
    pass

In this example, Dog is a simple class with no attributes or methods. The pass statement is used here as a placeholder since Python requires at least one statement in the indented block.

Class Naming Conventions

Python uses CapitalizedWords notation (CamelCase) for naming classes. This convention makes class names stand out against other identifiers, which usually use lowercase with underscores.

Here's an example:

class JackRussellTerrier:
    # Class body goes here
    pass

In this example, JackRussellTerrier follows the CapitalizedWords convention, clearly indicating that it is a class.

The __init__ Method

The __init__ method is the initializer or constructor of a class. It's automatically invoked when a new instance of the class is created. The first parameter of this method is always self, which represents the instance of the class. Through self, you can access the attributes and methods of the class.

Here's an example with the __init__ method:

class Employee:
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id

In this Employee class, the __init__ method takes three parameters: self, name, and employee_id. The self parameter is a reference to the current instance of the class and is used to access variables that belong to the class. name and employee_id are attributes of the class that are initialized with the values passed during the creation of an instance. When you create a new class instance, then Python automatically passes the instance to the self parameter in .init() so that Python can define the new  attributes  on the object.

Here's how you would create an instance of Employee:

employee = Employee("Alice", 123)

In this instance, "Alice" and 123 are passed to the __init__ method and are used to initialize the name and employee_id attributes of the employee instance.

.init() method has three parameters, so why are you only passing two arguments to it in the example?
When you instantiate the Employee class, Python creates a new instance of Employee and passes it to the first parameter of .init(). This essentially removes the self parameter, so you only need to worry about the name and employee_id parameters.

Instance Attributes vs Class Attributes in Python

In Python, understanding the difference between instance attributes and class attributes is crucial for designing and managing classes effectively. Let's dive into this with code examples.

Instance Attributes

Instance attributes are associated with a specific instance of a class. Each instance has its own copy of these attributes, meaning that changes to an instance attribute in one instance do not affect the same attribute in another instance.

Here's an example to illustrate instance attributes:

class Employee:
    def __init__(self, name, employee_id):
        # These are instance attributes
        self.name = name
        self.employee_id = employee_id

# Creating two instances of Employee
alice = Employee("Alice", 123)
bob = Employee("Bob", 456)

# Each instance has its own attributes
print(alice.name)  # Outputs: Alice
print(bob.name)    # Outputs: Bob

In this example, name and employee_id are instance attributes. Each Employee instance (alice and bob) has its own separate values for these attributes.

Class Attributes

Class attributes, on the other hand, are attributes that are shared by all instances of the class. Changing the value of a class attribute affects all instances at the same time.

Here's an example demonstrating class attributes:

class Employee:
    # This is a class attribute
    company_name = "TechCorp"

    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id

# Creating instances of Employee
alice = Employee("Alice", 123)
bob = Employee("Bob", 456)

# Accessing the class attribute
print(alice.company_name)  # Outputs: TechCorp
print(bob.company_name)    # Outputs: TechCorp

# Changing the class attribute
Employee.company_name = "NewTechCorp"

# Both instances reflect the change
print(alice.company_name)  # Outputs: NewTechCorp
print(bob.company_name)    # Outputs: NewTechCorp

In this case, company_name is a class attribute. It's shared by all instances of the Employee class. Changing company_name via the class Employee affects all instances (alice and bob).

Difference between class and instance attributes?
Instance Attributes: Unique to each instance. Changes to an instance attribute only affect that specific instance. Class Attributes: Shared by all instances of a class. Changing a class attribute affects all instances of the class.

Object Construction or Instantiation in Python

Creating and initializing objects of a given class is a fundamental concept in Object-Oriented Programming (OOP). This process is often referred to as object construction or instantiation. In Python, this is handled through what is known as a class constructor.

Class Constructor in Python

A class constructor is a special block of code that is invoked when a new object of a class is created. It sets up the new object, initializing its attributes and performing any other necessary setup. In Python, the constructor is the __init__ method, which you've seen in previous examples.

Here's a closer look at how this works:

  1. Calling the Class Constructor: When you call a class in Python, you are essentially calling its constructor. This action creates a new instance of the class.

  2. Initialization Process: The constructor (__init__ method) initializes the new object. It's common to pass initial values to the constructor, which then assigns these values to the object's attributes.

  3. Returning the New Object: After initialization, the newly created object is returned and can be assigned to a variable.

Let's look at an example to illustrate this process:

class Employee:
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id

# Creating an instance of Employee
alice = Employee("Alice", 123)

In this example:

  • The Employee class has an __init__ method, which is its constructor.

  • When we create the alice instance by calling Employee("Alice", 123), the __init__ method is automatically invoked.

  • Inside the __init__ method, the name and employee_id attributes of the alice object are initialized with the values "Alice" and 123.

What did you learn in this section?
Object Construction/Instantiation: The process of creating and initializing objects.Class Constructor: In Python, the class constructor is the init method, responsible for setting up new objects.

For more details on constructors: Python Class Constructors: Control Your Object Instantiation – Real Python

Printing a Class: The __str__ Method and Dunder Methods in Python

In Python, special methods that are meant to represent or implement behavior for built-in operations are often referred to as "dunder" methods, due to their naming convention of double underscores at the beginning and end. Two common examples of such methods are __init__ and __str__. Understanding and utilizing these dunder methods is a key aspect of mastering Object-Oriented Programming (OOP) in Python.

The __str__ Method

The __str__ method is a special method that is called when you use the print function on an object or when you use the str() function to convert an object to a string. It returns a string representation of the object, which can be very useful for debugging and logging purposes.

Here's how you can define and use the __str__ method:

class Employee:
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id

    def __str__(self):
        return f"Employee(Name: {self.name}, ID: {self.employee_id})"

# Creating an instance of Employee
alice = Employee("Alice", 123)

# Using the __str__ method
print(alice)  # Outputs: Employee(Name: Alice, ID: 123)

In this example, when print(alice) is executed, the __str__ method of the Employee class is called, which returns a formatted string containing the name and ID of the employee.

Dunder Methods in Python

Dunder methods, also known as magic methods, allow you to emulate the behavior of built-in types or to implement operator overloading. For example:

  • __len__: Called to implement the len() built-in function.

  • __add__: Allows definition of a custom behavior for the + operator.

  • __iter__, __next__: Implement iterator protocols.

Each of these methods provides a way to use Python's built-in syntax and operations with your custom objects, making your classes more intuitive and integrated into the language.

Class Inheritance and Method Overriding

Class inheritance is a fundamental aspect of Object-Oriented Programming (OOP) in Python, allowing for the creation of a new class that inherits attributes and methods from an existing class. This feature facilitates code reuse and helps in creating a hierarchical organization of classes.

Class Inheritance

Inheritance enables a new class to inherit properties and methods from another class. The new class, known as the child class, can add new attributes and methods or modify existing ones from the parent class.

Here's a basic example of inheritance:

class Animal:
    def __init__(self, species):
        self.species = species

    def make_sound(self):
        pass

class Dog(Animal):
    def __init__(self, species, name):
        super().__init__(species)
        self.name = name

    def make_sound(self):
        return "Bark!"

# Using the Dog class
buddy = Dog("Canine", "Buddy")
print(buddy.make_sound())  # Outputs: Bark!

In this example, Dog inherits from Animal. The Dog class has its own __init__ method and overrides the make_sound method.

The super().__init__ Method

The super() function returns a temporary object of the superclass, allowing you to call its methods. This is most often used in the __init__ method to ensure that the parent class is properly initialized:

super().__init__(species)

This line in the Dog class's __init__ method ensures that the Animal class's __init__ method is called, allowing Dog to initialize the inherited species attribute.

Method Overriding

Method overriding occurs when a child class provides a specific implementation of a method already defined in its parent class. In the above example, Dog overrides the make_sound method defined in Animal. This allows Dog instances to have a different implementation of make_sound, specifically returning "Bark!".

Operator Overloading

Operator overloading allows custom classes to redefine how Python's built-in operators behave. This is achieved by defining special methods in the class, such as __add__ for +, __sub__ for -, etc.

Here's an example:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

# Using the Vector class
v1 = Vector(2, 3)
v2 = Vector(5, 7)
result = v1 + v2
print(f"Result: ({result.x}, {result.y})")  # Outputs: Result: (7, 10)

In this example, the Vector class defines the __add__ method, enabling the addition of two Vector instances using the + operator.

Library Management System

Let's consolidate everything we've learned through a real-world example: a simple library management system. This system will involve classes for books, members, and the library itself, showcasing class definitions, inheritance, super().__init__, method overriding, and operator overloading.

Classes: Book and Member

We'll start by defining two basic classes, Book and Member, to represent books and library members.

class Book:
    def __init__(self, title, author, isbn):
        self.title = title
        self.author = author
        self.isbn = isbn

    def __str__(self):
        return f"'{self.title}' by {self.author} (ISBN: {self.isbn})"

class Member:
    def __init__(self, name, member_id):
        self.name = name
        self.member_id = member_id
        self.books_checked_out = []

    def check_out_book(self, book):
        self.books_checked_out.append(book)

    def __str__(self):
        return f"Member: {self.name}, ID: {self.member_id}, Books: {len(self.books_checked_out)}"

Class: Library (Inheriting from Member)

The Library class will inherit from Member and include additional functionality, demonstrating inheritance and method overriding.

class Library(Member):
    def __init__(self, name):
        super().__init__(name, 'LIBRARY')
        self.members = []

    def add_member(self, member):
        self.members.append(member)

    def __str__(self):
        return f"Library: {self.name}, Members: {len(self.members)}"

Real-World Usage

Now, let's create instances and demonstrate how these classes can be used in a real-world scenario:

# Creating books
book1 = Book("1984", "George Orwell", "123456789")
book2 = Book("To Kill a Mockingbird", "Harper Lee", "987654321")

# Creating a member
alice = Member("Alice", 101)

# Alice checks out a book
alice.check_out_book(book1)

# Creating a library
my_library = Library("Central Library")
my_library.add_member(alice)

# Printing information
print(book1)          # Outputs book details
print(alice)          # Outputs member details
print(my_library)     # Outputs library details

This simple library management system exemplifies the core principles of Object-Oriented Programming in Python. We've seen how classes can encapsulate data and behavior, how inheritance allows for code reuse and organization, and how methods like __str__ can be overridden to provide meaningful string representations of objects.

The example demonstrates that OOP in Python is not just a programming technique but a way of structuring and conceptualizing code that mirrors real-world systems. This approach leads to more organized, scalable, and maintainable code, making it easier to adapt and extend over time. As you dive deeper into Python and OOP, you'll discover even more tools and patterns that can help solve complex programming challenges in an elegant and efficient way.

RESOURCES: I personally find this link to be very helpful for beginners in OOP: Object-Oriented Programming (OOP) in Python 3 – Real Python

That's all for OOP in Python. Keep learning, practicing and exploring. Till then...

Happy Coding Folks!

More from this blog

Code and Query

11 posts

👋 Hi there! I'm Kritisha Panda, the voice and brains behind Code and Query. Welcome to my digital nook where code meets creativity! A Data Science enthusiast and a Python explorer. Let's connect!