Object-Oriented Programming

Chapter 8: Object-Oriented Programming

Object-Oriented Programming (OOP) organizes code around objects—bundles of data and related functions. Instead of separate variables and functions scattered throughout your code, OOP groups them into logical units called classes. This approach models real-world concepts naturally and creates reusable, maintainable code.

For example, instead of separate variables for car_make, car_model, car_year and functions drive_car(), brake_car(), you create a Car class containing all car-related data and behavior. This organization becomes essential as programs grow in complexity and scale.

Classes and Objects

A class is a blueprint for creating objects. It defines what data (attributes) and behavior (methods) objects will have. An object is a specific instance of a class—a concrete realization of the blueprint.

Here, Dog is the class (blueprint), while buddy and max are objects (instances). Each object is independent—changes to one don't affect the other.

The init Constructor

The __init__ method initializes new objects, setting their initial state. It runs automatically when you create an instance:

The __init__ method takes self (the object being created) plus any parameters you define. Inside, you set attributes using self.attribute_name = value.

Understanding self

The self parameter represents the instance itself—the specific object calling the method. Python passes it automatically; you don't provide it when calling methods:

Each instance maintains its own count value. The self parameter ensures methods operate on the correct object.

Methods: Object Behavior

Methods are functions defined inside classes. They operate on object data (attributes) and define object behavior:

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        print(f"Deposited ${amount}. New balance: ${self.balance}")

    def withdraw(self, amount):
        if amount > self.balance:
            print("Insufficient funds!")
        else:
            self.balance -= amount
            print(f"Withdrew ${amount}. New balance: ${self.balance}")

account = BankAccount("Alice", 100)
account.deposit(50)   # Deposited $50. New balance: $150
account.withdraw(30)  # Withdrew $30. New balance: $120

Methods access and modify object attributes through self. This encapsulation keeps related data and operations together.

Attributes: Object State

Attributes store object data. Instance attributes (set with self) are unique to each object. Class attributes (set at class level) are shared across all instances:

class Circle:
    # Class attribute (shared by all circles)
    pi = 3.14159

    def __init__(self, radius):
        # Instance attribute (unique to each circle)
        self.radius = radius

    def area(self):
        return Circle.pi * self.radius ** 2

    def circumference(self):
        return 2 * Circle.pi * self.radius

c1 = Circle(5)
c2 = Circle(10)

print(c1.area())  # Uses c1's radius (5)
print(c2.area())  # Uses c2's radius (10)
print(Circle.pi)  # Access class attribute through class name

Use instance attributes for data unique to each object. Use class attributes for constants or data shared across all instances.

Inheritance: Code Reuse

Inheritance lets classes inherit attributes and methods from other classes. The child (subclass) extends or modifies parent (superclass) behavior:

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

    def speak(self):
        print(f"{self.name} makes a sound")

class Dog(Animal):  # Dog inherits from Animal
    def speak(self):  # Override parent method
        print(f"{self.name} barks: Woof!")

class Cat(Animal):  # Cat inherits from Animal
    def speak(self):  # Override parent method
        print(f"{self.name} meows: Meow!")

dog = Dog("Buddy")
cat = Cat("Whiskers")

dog.speak()  # Output: Buddy barks: Woof!
cat.speak()  # Output: Whiskers meows: Meow!

The Dog and Cat classes inherit __init__ from Animal but provide their own speak methods. Inheritance promotes code reuse and logical organization.

Extending Parent Behavior with super()

Use super() to call parent methods while adding child-specific behavior:

class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def info(self):
        return f"{self.brand} {self.model}"

class ElectricCar(Vehicle):
    def __init__(self, brand, model, battery_capacity):
        super().__init__(brand, model)  # Call parent constructor
        self.battery_capacity = battery_capacity

    def info(self):
        base_info = super().info()  # Get parent info
        return f"{base_info} (Battery: {self.battery_capacity} kWh)"

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

The super() function calls parent methods, letting you extend rather than completely replace functionality.

Practical OOP Patterns

Data Modeling:

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages
        self.current_page = 1

    def read(self, pages):
        self.current_page += pages
        if self.current_page > self.pages:
            self.current_page = self.pages
        print(f"You're on page {self.current_page} of {self.pages}")

book = Book("Python Guide", "Alice", 300)
book.read(50)  # You're on page 51 of 300

Composition (Has-A Relationship):

class Address:
    def __init__(self, street, city, zipcode):
        self.street = street
        self.city = city
        self.zipcode = zipcode

class Person:
    def __init__(self, name, address):
        self.name = name
        self.address = address  # Person has an Address

    def info(self):
        return f"{self.name} lives at {self.address.street}, {self.address.city}"

addr = Address("123 Main St", "NYC", "10001")
person = Person("Bob", addr)
print(person.info())

Best Practices

  1. Use meaningful class names - Use nouns that describe what the class represents (Car, User, BankAccount)
  2. Keep classes focused - Each class should have a single, clear purpose
  3. Use inheritance wisely - Only inherit when there's a true "is-a" relationship (Dog is-a Animal)
  4. Encapsulate data - Keep attributes private when they shouldn't be modified directly
  5. Document with docstrings - Explain what your classes do and how to use them

OOP isn't always necessary—simple scripts work fine with functions. But as programs grow, OOP provides structure that keeps code organized, maintainable, and scalable.

Try It Yourself: Practice Exercises

Exercise 1: Rectangle Class
Create a Rectangle class with width and height attributes. Add methods for area() and perimeter().

Exercise 2: Student Class
Create a Student class with name and grades (list) attributes. Add methods to add_grade(grade) and average() that calculates the average grade.

Exercise 3: Temperature Class
Create a Temperature class that stores Celsius. Add methods to_fahrenheit() and to_kelvin() that return conversions.

Exercise 4: Inheritance Example
Create a Shape base class with a describe() method. Create Square and Triangle subclasses that inherit from Shape and override describe().

Exercise 5: Shopping Cart
Create a Product class (name, price) and ShoppingCart class. The cart should have methods to add_product(product), remove_product(name), and total() that calculates the sum.

Exercise 6: Counter with Reset
Create a Counter class with increment(), decrement(), get_value(), and reset() methods.

What's Next?

You've mastered Python's object-oriented programming fundamentals: creating classes and objects, using constructors, defining methods, working with attributes, and implementing inheritance. OOP is a powerful paradigm for organizing complex programs and modeling real-world concepts in code.

In the next chapter, we'll explore working with APIs—making HTTP requests, handling JSON data, and integrating with external services. You'll learn to fetch data from web services and build programs that interact with the broader internet.

Continue to Chapter 9: Working with APIs

📝 Test Your Knowledge: Object-Oriented Programming

Take this quiz to reinforce what you've learned in this chapter.