Python Classes and Objects

Learn about classes and objects in Python, including their creation, methods, and attributes.

Last updated: 2024-12-24

Today, we're going to dive into one of the most crucial topics in Python programming - Classes and Objects. These concepts form the backbone of Object-Oriented Programming (OOP) paradigm and play a vital role in creating complex programs.

What are Classes?

A class is a blueprint or a template that defines the attributes (properties) and behaviors (methods) of objects. It combines data and functions to create a new data type.

Let's start with a simple example:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
    
    def info(self):
        return f"{self.year} {self.make} {self.model}"

Here:

  • Car is the name of the class.
  • __init__ is the constructor method, called when a new object is created.
  • self refers to the current instance of the class.
  • info is a class method.

What are Objects?

An object is a specific instance of a class. If a class is a blueprint, an object is a concrete thing created from that blueprint.

Let's create an object from our Car class:

my_car = Car("Toyota", "Camry", 2022)
print(my_car.info())  # Output: 2022 Toyota Camry

Here, my_car is an object of the Car class.

Attributes and Methods

Attributes

Attributes are data that belong to a class or an object. They are of two types:

  1. Instance attributes: Specific to each object.
  2. Class attributes: Declared at the class level and shared by all objects.
class Car:
    car_count = 0  # Class attribute
    
    def __init__(self, make, model):
        self.make = make  # Instance attribute
        self.model = model  # Instance attribute
        Car.car_count += 1

Methods

Methods are functions defined within a class. They are of three types:

  1. Instance methods: Regular methods with self parameter.
  2. Class methods: Decorated with @classmethod, receive cls parameter.
  3. Static methods: Decorated with @staticmethod, receive neither self nor cls.
class Car:
    car_count = 0
    
    def __init__(self, make, model):
        self.make = make
        self.model = model
        Car.car_count += 1
    
    def info(self):  # Instance method
        return f"{self.make} {self.model}"
    
    @classmethod
    def total_cars(cls):  # Class method
        return f"Total number of cars: {cls.car_count}"
    
    @staticmethod
    def company_info():  # Static method
        return "This is the Car class"

Inheritance

Inheritance is a mechanism of basing one class upon another class. It promotes code reusability and allows creating hierarchical relationships.

class ElectricCar(Car):
    def __init__(self, make, model, battery_capacity):
        super().__init__(make, model)
        self.battery_capacity = battery_capacity
    
    def info(self):
        return f"{super().info()}, Battery capacity: {self.battery_capacity} kWh"

tesla = ElectricCar("Tesla", "Model 3", 75)
print(tesla.info())  # Output: Tesla Model 3, Battery capacity: 75 kWh

Here, the ElectricCar class inherits from the Car class and extends its functionality.

Encapsulation

Encapsulation is the bundling of data and the methods that operate on that data. In Python, this is typically done by convention, as Python doesn't fully support "private" attributes.

class BankAccount:
    def __init__(self, owner, balance):
        self.__owner = owner  # Private attribute
        self.__balance = balance  # Private attribute
    
    def display_balance(self):
        return f"Balance: ${self.__balance}"
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return f"${amount} deposited. {self.display_balance()}"
        return "Invalid amount"

account = BankAccount("Alice", 1000)
print(account.display_balance())  # Output: Balance: $1000
print(account.deposit(500))  # Output: $500 deposited. Balance: $1500
# print(account.__balance)  # This would raise an AttributeError

Polymorphism

Polymorphism is the ability to use methods with the same name from different classes. It enhances code flexibility.

class Animal:
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

animals = [Dog(), Cat()]
for animal in animals:
    print(animal.speak())
# Output:
# Woof!
# Meow!

Practical Exercise

Let's create a small project to reinforce our understanding. We'll model a library system:

class Book:
    def __init__(self, title, author, ISBN, publication_year):
        self.title = title
        self.author = author
        self.ISBN = ISBN
        self.publication_year = publication_year
        self.available = True
    
    def info(self):
        status = "Available" if self.available else "Checked out"
        return f"'{self.title}' by {self.author}, {self.publication_year} ({status})"

class Library:
    def __init__(self, name):
        self.name = name
        self.books = []
    
    def add_book(self, book):
        self.books.append(book)
        return f"'{book.title}' has been added to the library"
    
    def search_book(self, title):
        for book in self.books:
            if book.title.lower() == title.lower():
                return book.info()
        return "Book not found"
    
    def check_out_book(self, title):
        for book in self.books:
            if book.title.lower() == title.lower() and book.available:
                book.available = False
                return f"'{book.title}' has been checked out"
        return "Book is not available or already checked out"

# Create a library and add books
my_library = Library("My Library")
book1 = Book("To Kill a Mockingbird", "Harper Lee", "978-0446310789", 1960)
book2 = Book("1984", "George Orwell", "978-0451524935", 1949)

print(my_library.add_book(book1))
print(my_library.add_book(book2))

# Search for and check out a book
print(my_library.search_book("To Kill a Mockingbird"))
print(my_library.check_out_book("To Kill a Mockingbird"))
print(my_library.search_book("To Kill a Mockingbird"))

This project demonstrates the use of classes, objects, methods, and attributes, as well as the principles of encapsulation and polymorphism in practice.

Conclusion

Classes and Objects are fundamental concepts of OOP in Python. They provide powerful tools for organizing code, promoting reusability, and modeling complex systems. By understanding and applying these concepts well, you can create more efficient and flexible programs.

Additional Resources

  1. Python Official Documentation - Classes
  2. Real Python - Object-Oriented Programming (OOP) in Python 3
  3. Coursera - Python Classes and Inheritance