What is Object-Oriented Programming (OOP)?

OOP is a programming paradigm based on the concept of "objects". An object is a collection of data (attributes) and methods (functions) that act on that data. Think of a real-world object like a car. A car has attributes (color, brand, speed) and methods (accelerate, brake). OOP allows us to model real-world things in our code.

The class Keyword: Creating a Blueprint

A class is a blueprint for creating objects. It defines the attributes and methods that all objects of that class will have. We use the class keyword to define a class. By convention, class names start with a capital letter (PascalCase).

Python


class Dog:
    # This is a class attribute, shared by all instances of the class
    species = "Canis familiaris"

    # This is the initializer/constructor method
    def __init__(self, name, age):
        # These are instance attributes, unique to each object
        self.name = name
        self.age = age

    # This is an instance method
    def bark(self):
        return "Woof!"

The __init__ Method: The Constructor

The __init__ method is a special method called a constructor. It's automatically called when you create a new object (instance) of the class. Its job is to initialize the object's attributes.

The self parameter is a reference to the current instance of the class and is used to access variables that belong to the class. It is always the first parameter of any instance method.

Creating Objects (Instances)

An object (or instance) is a specific creation based on the class blueprint. You create an instance by "calling" the class name as if it were a function.

Python


# Create two Dog objects (instances)
dog1 = Dog("Buddy", 3)
dog2 = Dog("Lucy", 5)

# Access instance attributes
print(f"{dog1.name} is {dog1.age} years old.") # Output: Buddy is 3 years old.
print(f"{dog2.name} is a {dog2.species}.")     # Output: Lucy is a Canis familiaris.

# Call an instance method
print(f"{dog1.name} says: {dog1.bark()}")     # Output: Buddy says: Woof!

Dunder Methods: The Magic Behind the Scenes

Methods that start and end with double underscores, like __init__, are called "dunder" methods (for Double Underscore). They allow you to integrate your custom objects with Python's built-in behavior.

__str__(self)

This method is called by the print() and str() functions. It should return a user-friendly string representation of the object.

__repr__(self)

This method is called to get an "official," unambiguous string representation of an object, which is often used for debugging. The goal of __repr__ is to be able to recreate the object if possible.

Let's add these to our Dog class:

Python


class Dog:
    species = "Canis familiaris"

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

    def bark(self):
        return "Woof!"

    def __str__(self):
        # User-friendly representation
        return f"{self.name}, the {self.age}-year-old dog"

    def __repr__(self):
        # Developer-friendly, unambiguous representation
        return f"Dog(name='{self.name}', age={self.age})"

my_dog = Dog("Rex", 4)
print(my_dog)         # Calls __str__ -> Output: Rex, the 4-year-old dog
print(repr(my_dog))   # Calls __repr__ -> Output: Dog(name='Rex', age=4)