Classes and Objects in Python

In Python, classes and objects are fundamental concepts in object-oriented programming (OOP). They help in structuring your code by organizing data and functions into reusable units.

Defining 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 will have.

Python
# Define a class
class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def bark(self):
        return "Woof!"

# Create an object of the class
my_dog = Dog(name="Buddy", age=4)

# Access object attributes
print(my_dog.name)  # Output: Buddy
print(my_dog.age)   # Output: 4

# Call object method
print(my_dog.bark())  # Output: Woof!

In this example, the Dog class has an initializer method __init__ to set up the object’s attributes, and a method bark to perform an action. The my_dog object is created from this class, and its attributes and methods are accessed using dot notation.

Instance Variables

Instance variables are attributes specific to an object. They are defined within the __init__ method and are accessed using self.

Python
class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

# Create an object of the class
my_car = Car(make="Toyota", model="Corolla", year=2020)

# Access instance variables
print(my_car.make)  # Output: Toyota
print(my_car.model)  # Output: Corolla
print(my_car.year)   # Output: 2020

Here, make, model, and year are instance variables of the Car class. Each Car object will have its own set of these variables.

Methods

Methods are functions defined inside a class that operate on the class's instance variables.

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

    def greet(self):
        return f"Hello, my name is {self.name} and I am {self.age} years old."

# Create an object of the class
person = Person(name="Alice", age=30)

# Call a method
print(person.greet())  # Output: Hello, my name is Alice and I am 30 years old.

The greet method in the Person class returns a greeting string that includes the person's name and age.

Inheritance

Inheritance allows a class to inherit attributes and methods from another class. This promotes code reuse and creates a hierarchy between classes.

Python
class Animal:
    def speak(self):
        return "Some sound"

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

# Create an object of the subclass
my_cat = Cat()

# Call inherited and own methods
print(my_cat.speak())  # Output: Some sound
print(my_cat.meow())   # Output: Meow!

The Cat class inherits from the Animal class, so it has access to the speak method. It also has its own method meow.

Encapsulation

Encapsulation involves bundling data (attributes) and methods that operate on the data into a single unit, i.e., a class. It also involves restricting direct access to some of the object's components.

Python
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # private attribute

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

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

    def get_balance(self):
        return self.__balance

# Create an object of the class
account = BankAccount(balance=1000)

# Access methods
account.deposit(500)
print(account.get_balance())  # Output: 1500

account.withdraw(200)
print(account.get_balance())  # Output: 1300

The BankAccount class uses a private attribute __balance to restrict direct access from outside the class. It provides methods for deposit, withdrawal, and balance retrieval.

Polymorphism

Polymorphism allows different classes to be treated as instances of the same class through a common interface. It’s achieved through method overriding and method overloading.

Python
class Shape:
    def area(self):
        raise NotImplementedError("Subclasses must implement this method")

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

# Create objects of different shapes
circle = Circle(radius=5)
rectangle = Rectangle(width=4, height=6)

# Call the area method
print(circle.area())       # Output: 78.5
print(rectangle.area())   # Output: 24

Here, both Circle and Rectangle classes implement the area method in their own way. This is an example of polymorphism where the same method name is used for different implementations.