← Previous

Lesson 19

Modules and organizing code

~35 min

1. OOP & Modules in Python

As your Python programs grow from small scripts into full applications, organizing code becomes your most important job. Object-Oriented Programming (OOP) allows you to model real-world concepts into reusable components, while Modules let you break giant script files into clean, manageable, multi-file projects.

Learning Objectives

  • Understand the core principles of Object-Oriented Programming (OOP): Classes and Objects.
  • Master the constructor __init__() method and the self parameter.
  • Define custom instance attributes and methods.
  • Create, import, and organize custom Python modules using import syntax.
  • Protect executable code using the if __name__ == "__main__": idiom.

2. Core OOP Concepts: Classes & Objects

At its heart, Object-Oriented Programming is built around two key concepts:

  • Class: A blueprint or template for creating objects. It defines what data an object holds (attributes) and what actions it can perform (methods).
  • Object (Instance): An individual, concrete instance created from a class blueprint.

Think of a Class like an architectural blueprint for a house. The blueprint itself isn't a physical house, but it defines the structure (number of rooms, doors, color). An Object is the actual physical house built using that blueprint. You can build 50 different houses (objects) from a single blueprint (class)!

3. Anatomy of a Class: __init__ and self

In Python, classes are created using the class keyword. Inside a class, we use a special built-in method named __init__() (the constructor) to initialize an object's starting data when it is created.

The self parameter represents the specific object instance currently being acted upon. Python passes self automatically under the hood whenever you call an instance method.

Python code example:

class Dog:
    # Constructor method runs automatically when creating a new object
    def __init__(self, name, breed, age):
        # Instance attributes
        self.name = name
        self.breed = breed
        self.age = age

    # Instance method (action the object can take)
    def bark(self):
        return f"{self.name} says Woof!"

# Creating instances (objects) of the Dog class
dog1 = Dog("Buddy", "Golden Retriever", 3)
dog2 = Dog("Luna", "Husky", 2)

# Accessing attributes and calling methods
print(f"{dog1.name} is a {dog1.breed}.")  # Output: Buddy is a Golden Retriever.
print(dog2.bark())                         # Output: Luna says Woof!

4. Working with Python Modules

A Module in Python is simply a single file containing Python code (with a .py extension)—such as functions, classes, and variables. Modules allow you to divide large projects into separate files to keep your codebase clean and scalable.

You can import built-in Python modules (like math, random, or datetime) or create your own custom modules!

Module Import Techniques

Syntax Pattern Example How to Call Functions Best Practice Recommendation
Full Import import math math.sqrt(16) Best for readability; avoids naming conflicts.
Specific Import from math import sqrt sqrt(16) Great when you only need 1 or 2 specific items.
Aliased Import import random as rnd rnd.randint(1, 10) Ideal for long module names or common aliases (e.g., pd for pandas).

5. Creating & Guarding Custom Modules

Suppose you create a file named bank_utils.py containing helper functions and classes. To prevent code inside bank_utils.py from running automatically when imported by another script, wrap test code inside if __name__ == "__main__":.

File 1: bank_utils.py (Custom Module)

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

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            return True
        return False

# Execution Guard: Runs ONLY when this file is executed directly!
if __name__ == "__main__":
    test_acc = BankAccount("Test User", 100)
    print("Testing module locally: Account created successfully!")

File 2: main.py (Main Application)

# Importing custom class from our custom module
from bank_utils import BankAccount

account = BankAccount("Alice", 500.0)
account.deposit(250.0)
print(f"{account.owner}'s Balance: ${account.balance:.2f}")  # Output: Alice's Balance: $750.00

6. Real World Example: E-Commerce Shopping Cart

Combining classes and objects allows you to easily model complex business logic like shopping carts, products, and checkout operations:

class Product:
    def __init__(self, product_id, name, price):
        self.product_id = product_id
        self.name = name
        self.price = price

class ShoppingCart:
    def __init__(self, customer_name):
        self.customer_name = customer_name
        self.items = []  # List holding Product instances

    def add_product(self, product):
        self.items.append(product)
        print(f"Added {product.name} to {self.customer_name}'s cart.")

    def calculate_total(self):
        return sum(product.price for product in self.items)

# Usage Example
p1 = Product(101, "Wireless Mouse", 29.99)
p2 = Product(102, "Mechanical Keyboard", 89.99)

cart = ShoppingCart("Sarah")
cart.add_product(p1)
cart.add_product(p2)

print(f"Cart Total: ${cart.calculate_total():.2f}")  # Output: Cart Total: $119.98

7. Common Beginner Mistakes

⚠️ Watch out for these mistakes:

  • Forgetting self as the First Parameter: Defining instance methods as def bark(): instead of def bark(self): causes a TypeError: bark() takes 0 positional arguments but 1 was given when called.
  • Forgetting self. When Assigning Attributes: Writing name = name inside __init__ creates a temporary local variable rather than storing an attribute on the object! Always use self.name = name.
  • Confusing Class Attributes vs. Instance Attributes: Defining variables directly under the class block creates a single variable shared across ALL instances, whereas instance attributes set inside __init__ belong uniquely to each instance.
  • Circular Imports: Importing moduleA inside moduleB while simultaneously importing moduleB inside moduleA will cause an ImportError. Keep your module hierarchy linear.

8. Code Examples

Here are standard, production-ready OOP and module patterns in Python:

# 1. Standard Class Definition with Default Values
class User:
    def __init__(self, username, email, is_admin=False):
        self.username = username
        self.email = email
        self.is_admin = is_admin

    def promote(self):
        self.is_admin = True
        print(f"User {self.username} was promoted to Admin.")

user1 = User("johndoe", "john@example.com")
user1.promote()

# 2. Importing Built-in Modules with Aliases
import random as rnd

dice_roll = rnd.randint(1, 6)
print(f"You rolled a {dice_roll}!")
Interactive Practice

Mini Practice: Book Class Builder

Create a class named Book with an __init__() method that accepts title, author, and pages. Add a method named get_summary() that returns a formatted string like: "’Title’ by Author (Pages pages)".

0% complete
Check when done
Quiz

Test Your Understanding

1. What role does the `self` parameter play in Python class methods?

2. Which special method is automatically invoked when creating a new instance of a class?

3. Why do developers use `if __name__ == "__main__":` inside custom module files?

4. What is a Python Module?

9. Challenge Project

Modular Library Inventory System: Create two Python files in the same directory:

  1. library_item.py: Defines a LibraryItem class with attributes title, item_id, and is_checked_out (default False). Include methods check_out() and return_item().
  2. main.py: Import the LibraryItem class, create three item instances (e.g. books/DVDs), simulate checking out two items, and print an updated inventory status summary!

10. Key Takeaways

  • A Class serves as a blueprint; an Object is a unique instance built from that blueprint.
  • Use the __init__() method to set up initial object attributes.
  • The self keyword gives instance methods access to the object's own attributes and methods.
  • Any Python file ending in .py can act as an importable Module.
  • Guard module test code with if __name__ == "__main__": to prevent accidental code execution upon import.

🚀 Progression

You've mastered Object-Oriented Programming and Modules! Next up, we will learn how to make our applications crash-proof with **Exception Handling in Python**!