Courses/Python/Object-Oriented Programming

    Lesson 12 β€’ Expert

    Object-Oriented Programming (OOP) in Python 🧠

    Master classes, objects, and design principles to write structured, reusable, professional-grade code.

    What You'll Learn in This Lesson

    • β€’ What a class is and how to create objects from it
    • β€’ Instance variables vs class variables
    • β€’ The four pillars: encapsulation, abstraction, inheritance, polymorphism
    • β€’ Writing __init__, __str__, and instance methods
    • β€’ When to use classes vs plain functions

    1️⃣ Introduction β€” Why Learn OOP

    🏠 Real-World Analogy

    Think of OOP like building with LEGO. Instead of one giant pile of bricks (procedural code), you have organized sets with instructions (classes) that you can use to build specific things (objects). You can reuse the same instructions to build multiple houses, cars, or robots!

    Up to now, you've written procedural code β€” a series of instructions that run top-to-bottom. That works for small scripts, but large projects quickly become messy.

    Object-Oriented Programming (OOP) fixes that. It organizes code around objects β€” bundles of data (attributes) and behavior (methods) that represent real-world concepts.

    ConceptWhat It MeansReal-World Example
    ClassA blueprint/templateA cookie cutter shape
    ObjectAn instance of a classAn actual cookie made from the cutter
    AttributeData/properties of an objectCookie's flavor, size, color
    MethodActions an object can doCookie can be eaten, decorated

    This structure powers everything from games and AI frameworks to mobile apps and APIs built in Python.

    2️⃣ What Are Classes and Objects?

    A class defines the blueprint or concept (like "Dog").
    An object (or instance) is a specific example of that concept (like "Buddy").

    πŸ’‘ How to Read This: The class Dog: line creates the blueprint. The dog1 = Dog("Buddy", 3) line creates an actual dog object named Buddy who is 3 years old.

    class Dog:                          # Step 1: Define the blueprint
        def __init__(self, name, age):  # Step 2: Setup method (runs when creating)
            self.name = name            # Step 3: Store the name
            self.age  = age             # Step 4: Store the age
    
        def bark(self):                 # Step 5: Define a behavior
            print(f"{self.name} says Woof!")
    
    # Creating objects (instances)
    dog1 = Dog("Buddy", 3)              # Creates a Dog named Buddy, age 3
    dog2 = Dog("Max", 5)                # Creates a Dog named Max, age 5
    
    dog1.bark()                         # Buddy barks
    dog2.bark()                         # Max barks
    Output:
    Buddy says Woof! Max says Woof!

    Each object has its own data (name, age) but shares the same class definition.

    Try It Yourself: OOP

    Practice creating classes and objects in Python

    Try it Yourself Β»
    Python
    # Object-Oriented Programming Practice
    
    class Dog:
        def __init__(self, name, age, breed):
            self.name = name
            self.age = age
            self.breed = breed
        
        def bark(self):
            print(f"{self.name} says Woof!")
        
        def describe(self):
            print(f"{self.name} is a {self.age}-year-old {self.breed}")
    
    # Create objects (instances)
    dog1 = Dog("Buddy", 3, "Golden Retriever")
    dog2 = Dog("Max", 5, "German Shepherd")
    
    # Call methods
    dog1.describe()
    dog1.bark()
    
    print()  # 
    ...

    3️⃣ Understanding the self Parameter

    πŸ”‘ Key Concept: What is "self"?

    self is like saying "me" or "this object". When a dog object calls bark(), self refers to THAT specific dog. It's how the object knows its own name, age, etc.

    Inside a class, every method receives self as the first argument. self represents the current object, allowing access to its own data.

    class Cat:
        def __init__(self, name):
            self.name = name       # self.name = "this cat's name"
    
        def meow(self):
            # self.name refers to THIS cat's name
            print(f"{self.name} says Meow!")
    
    cat = Cat("Luna")
    cat.meow()  # Luna says Meow!

    πŸ’‘ Remember: You MUST include self as the first parameter in every method inside a class. Python passes it automatically when you call the method β€” you never type it yourself!

    When you call cat.meow(), Python automatically passes that instance as self.

    4️⃣ Class Attributes vs Instance Attributes

    Class Attributes

    These are shared by all objects of that class.

    class Dog:
        species = "Canis familiaris"   # class attribute
    
        def __init__(self, name):
            self.name = name           # instance attribute
    
    dog1 = Dog("Buddy")
    dog2 = Dog("Max")
    
    print(Dog.species)  # Canis familiaris
    print(dog1.species) # Canis familiaris

    Change Dog.species once and it affects every dog.

    Instance Attributes

    Belong only to that object.

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

    Each dog keeps its own name and age.

    5️⃣ Adding Behavior with Methods

    Methods are functions inside classes that act on that object's data.

    class Calculator:
        def add(self, a, b):
            return a + b
    
        def multiply(self, a, b):
            return a * b
    
    calc = Calculator()
    print(calc.add(5, 3))
    print(calc.multiply(4, 6))
    Output:
    8 24

    Whenever you see a design that repeats logic, wrap it inside a method β€” it's cleaner and reusable.

    6️⃣ Constructors β€” __init__()

    🏠 Real-World Analogy

    __init__() is like the "birth certificate" of an object. When a baby is born, you record their name, birth date, etc. Similarly, when an object is created,__init__() sets up all its initial information.

    The special __init__() method (called a constructor) runs automatically every time you create a new object.

    PartMeaning
    def __init__(self, ...):Define the constructor method
    selfThe object being created
    self.name = nameSave the parameter as an attribute
    class Student:
        def __init__(self, name, grade):   # Runs automatically when creating
            self.name  = name              # Store name in the object
            self.grade = grade             # Store grade in the object
            print(f"Student {self.name} created.")
    
    s1 = Student("Boopie", "A+")  # __init__ runs here automatically!
    Output:
    Student Boopie created.

    You can think of __init__ as the setup stage for each object.

    7️⃣ Magic (Special) Methods in Python

    Python classes can override built-in behavior by defining dunder (double-underscore) methods.

    MethodPurposeExample
    __init__()ConstructorInitialize attributes
    __str__()Human-friendly stringUsed by print(obj)
    __repr__()Official representationUsed in debug tools
    __len__()Length behaviorMakes len(obj) work
    __add__()Add operatorobj1 + obj2
    __eq__()Equality checkobj1 == obj2

    Example:

    class Dog:
        def __init__(self, name):
            self.name = name
        def __str__(self):
            return f"Dog named {self.name}"
    
    dog = Dog("Buddy")
    print(dog)
    Output:
    Dog named Buddy

    8️⃣ Encapsulation β€” Protecting Data

    🏠 Real-World Analogy

    Encapsulation is like a vending machine. You can see what's available and insert money (public interface), but you can't reach inside and grab items directly (private data). The machine controls how you interact with it.

    Encapsulation means keeping data safe inside a class, exposing only what's needed. You hide details using private attributes (by convention, prefix with underscore).

    ConventionMeaningExample
    namePublic - accessible anywhereself.name
    _nameProtected - "please don't touch"self._balance
    __namePrivate - name mangling appliedself.__secret
    class BankAccount:
        def __init__(self, owner, balance):
            self.owner   = owner
            self._balance = balance  # "private" - don't access directly!
    
        def deposit(self, amount):
            if amount > 0:           # Validation! Can't deposit negative
                self._balance += amount
    
        def get_balance(self):       # Safe way to check balance
            return self._balance
    
    account = BankAccount("Brayan", 500)
    account.deposit(200)
    print(account.get_balance())     # βœ… Use the method
    # print(account._balance)        # ❌ Works but bad practice!

    This approach is standard in finance, games, and apps where data integrity matters.

    9️⃣ Abstraction β€” Simplify Complexity

    Abstraction hides unnecessary details so the user focuses on what matters.

    Example: You use print() without worrying about how text is sent to stdout.

    You can design your own abstractions:

    class CoffeeMachine:
        def brew(self):
            print("Heating water…")
            print("Grinding beans…")
            print("Pouring coffee…")
            print("Coffee ready!")

    All the complex steps stay hidden inside brew().

    πŸ”Ÿ Inheritance β€” Reuse Existing Code

    🏠 Real-World Analogy

    Inheritance is like family traits. A child inherits features from parents (eye color, height), but can also have their own unique features. In code, a "child" class inherits from a "parent" class.

    (Full lesson follows next module, but short preview here.)

    TermAlso CalledDescription
    Parent ClassBase / Super classThe class being inherited FROM
    Child ClassDerived / Sub classThe class that inherits

    Inheritance lets one class extend another:

    class Animal:                 # Parent class
        def speak(self):
            print("Some sound")
    
    class Dog(Animal):            # Child class - inherits from Animal
        def speak(self):          # Override parent's method
            print("Woof!")
    
    # Dog gets everything from Animal, plus its own version of speak()
    dog = Dog()
    dog.speak()  # Output: Woof!

    Now Dog inherits everything from Animal and customizes speak().

    11️⃣ Polymorphism β€” Same Name, Different Behavior

    🏠 Real-World Analogy

    Polymorphism means "many forms". Think of the word "open" β€” you can open a door, open a book, open an app. Same word, different actions depending on what you're opening. In code, different objects respond to the same method call in their own way.

    Different objects can share method names but act differently:

    class Bird:
        def make_sound(self): print("Chirp")
    
    class Cat:
        def make_sound(self): print("Meow")
    
    class Dog:
        def make_sound(self): print("Woof")
    
    # All have make_sound() but each does something different!
    animals = [Bird(), Cat(), Dog()]
    for animal in animals:
        animal.make_sound()  # Python figures out which version to call
    Output:
    Chirp Meow Woof

    πŸ’‘ Why This Matters: You can write code that works with ANY object that has a certain method, without knowing the exact type. This makes your code flexible and extensible!

    12️⃣ Designing Readable Classes

    Good Naming

    Use nouns for class names (Book, Student) and verbs for methods (calculate_total, display_info).

    Docstrings

    Document each class and method for clarity.

    class Rectangle:
        """Represents a 2D rectangle with width and height."""
        def __init__(self, w, h):
            self.width = w
            self.height = h

    13️⃣ Class vs Static Methods

    Class methods operate on the class itself, not instances.
    Static methods don't use self or cls β€” they're utility functions inside a class.

    TypeFirst ParameterUse Case
    Instance methodselfNeeds object data
    @classmethodclsNeeds class data, factory methods
    @staticmethodnoneUtility function, no class/object data needed
    class MathTools:
        @staticmethod
        def add(a, b):              # No self or cls needed
            return a + b
    
        @classmethod
        def identity(cls):          # cls = the class itself
            return f"This is class {cls.__name__}"
    
    # Call without creating an object!
    print(MathTools.add(3, 5))      # Output: 8
    print(MathTools.identity())     # Output: This is class MathTools

    14️⃣ Practical Example β€” Book Class πŸ“š

    class Book:
        def __init__(self, title, author, pages):
            self.title = title
            self.author = author
            self.pages = pages
    
        def __str__(self):
            return f"'{self.title}' by {self.author} ({self.pages} pages)"
    
    book = Book("1984", "George Orwell", 328)
    print(book)
    Output:
    '1984' by George Orwell (328 pages)

    15️⃣ Practical Example β€” Rectangle Class πŸ“

    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
    
        def area(self):
            return self.width * self.height
    
        def perimeter(self):
            return 2 * (self.width + self.height)
    
    rect = Rectangle(5, 10)
    print("Area:", rect.area())
    print("Perimeter:", rect.perimeter())

    16️⃣ Practical Example β€” Student Class πŸŽ“

    class Student:
        def __init__(self, name):
            self.name = name
            self.grades = []
    
        def add_grade(self, grade):
            self.grades.append(grade)
    
        def average(self):
            return sum(self.grades) / len(self.grades)
    
    s = Student("Boopie")
    s.add_grade(95)
    s.add_grade(88)
    s.add_grade(92)
    print("Average:", s.average())

    This model mirrors real-life objects β€” names, attributes, behaviors.

    17️⃣ Practical Example β€” ShoppingCart πŸ›’

    class ShoppingCart:
        def __init__(self):
            self.items = {}
    
        def add_item(self, name, price, quantity=1):
            self.items[name] = self.items.get(name, 0) + price * quantity
    
        def remove_item(self, name):
            if name in self.items: del self.items[name]
    
        def total(self):
            return sum(self.items.values())
    
    cart = ShoppingCart()
    cart.add_item("Apple", 1.5, 4)
    cart.add_item("Banana", 0.8, 6)
    print("Total:", cart.total())

    18️⃣ Advanced OOP Concepts β€” Deep Dive

    Inheritance in Detail

    Reuse and extend parent behavior.

    class Animal:
        def __init__(self, name):
            self.name = name
        def speak(self):
            return "Sound"
    
    class Dog(Animal):
        def speak(self):
            return "Woof!"
    
    dog = Dog("Buddy")
    print(dog.speak())

    Multiple Inheritance

    Python allows combining behaviors:

    class Flyer:
        def fly(self): print("Flying")
    
    class Swimmer:
        def swim(self): print("Swimming")
    
    class Duck(Flyer, Swimmer):
        pass
    
    d = Duck()
    d.fly()
    d.swim()

    Polymorphism in Practice

    Different objects responding to the same message:

    animals = [Dog("Max"), Animal("Creature")]
    for a in animals:
        print(a.speak())

    Composition β€” "Has a" Relationship

    Instead of inheriting, you can combine objects.

    class Engine:
        def start(self): print("Engine started.")
    
    class Car:
        def __init__(self):
            self.engine = Engine()
        def drive(self):
            self.engine.start()
            print("Car moving.")
    
    car = Car()
    car.drive()

    19️⃣ Practical Mini-Project β€” Library System πŸ“–

    Bringing it all together:

    class Book:
        def __init__(self, title, author):
            self.title = title
            self.author = author
            self.borrowed = False
    
    class Library:
        def __init__(self):
            self.books = []
    
        def add_book(self, book):
            self.books.append(book)
    
        def borrow_book(self, title):
            for b in self.books:
                if b.title == title and not b.borrowed:
                    b.borrowed = True
                    print(f"You borrowed '{title}'.")
                    return
            print("Book unavailable.")
    
    library = Library()
    library.add_book(Book("1984","Orwell"))
    library.add_book(Book("Dune","Herbert"))
    library.borrow_book("Dune")

    This project demonstrates encapsulation, composition, and real-world thinking in code design.

    Try It: Comprehensive OOP

    Practice classes, objects, and methods

    Try it Yourself Β»
    Python
    # Comprehensive OOP Example
    class Person:
        # Class attribute
        species = "Homo sapiens"
        
        def __init__(self, name, age):
            self.name = name
            self.age = age
        
        def introduce(self):
            print(f"Hi, I'm {self.name} and I'm {self.age} years old")
        
        def birthday(self):
            self.age += 1
            print(f"Happy birthday! Now {self.age} years old")
        
        def __str__(self):
            return f"Person({self.name}, {self.age})"
    
    # Create objects
    person1 = Person
    ...

    20️⃣ When to Use OOP

    • Your program models real-world entities (users, products, orders).
    • Code needs to be reused or extended.
    • You're building systems with multiple components (games, apps, APIs).

    If your code is short and one-off, functions may be enough. But for any project you plan to grow, OOP keeps it organized and maintainable.

    21️⃣ Best Practices βœ…

    • βœ” Each class should have a single responsibility.
    • βœ” Keep methods small and focused.
    • βœ” Use meaningful names.
    • βœ” Hide internal data with underscores.
    • βœ” Favor composition over deep inheritance.
    • βœ” Add docstrings for clarity.
    • βœ” Test classes individually.

    These habits match professional guidelines (PEP 8 and SOLID principles).

    22️⃣ Common Errors and Fixes 🧯

    ErrorCauseSolution
    TypeError: missing 1 required positional argumentForgot self in method definitionAdd self as first parameter
    AttributeErrorAccessing nonexistent attributeCheck name and constructor
    NameErrorUsed variable before definingInitialize inside __init__()
    Circular importTwo modules import each otherRestructure code into packages

    Remember that Python module names are case-sensitive on most systems.

    🎯 23️⃣ Mini Challenges

    1. Book Class: title, author, pages, and __str__.
    2. Rectangle: methods for area & perimeter.
    3. Student: add grades and average method.
    4. ShoppingCart: add/remove items, calculate total.
    5. Library: add borrow/return logic.
    6. BankAccount: deposit, withdraw, balance with safety checks.

    Practice these to solidify every OOP concept you've learned.

    24️⃣ Expert Insights (For Future Learning)

    • Learn design patterns (singleton, factory, observer).
    • Use dataclasses (from dataclasses import dataclass) to auto-generate boilerplate.
    • Explore typing annotations for cleaner interfaces.
    • Apply inheritance in Flask, FastAPI, and Django views.
    • Practice with real APIs that use OOP under the hood (Discord bots, Turtle graphics).

    25️⃣ Summary 🧾

    OOP turns code into living models of real systems.

    You can now:

    • Define classes and objects.
    • Use encapsulation and abstraction to organize data.
    • Reuse logic through inheritance and polymorphism.
    • Write cleaner, scalable, professional Python programs.

    With OOP mastered, you're ready to explore Inheritance and Polymorphism in depth in the next lesson.

    πŸ“‹ Quick Reference β€” OOP

    class Dog:Define a class
    def __init__(self, name):Constructor / initialiser
    self.name = nameSet instance variable
    Dog("Rex")Create an object
    isinstance(d, Dog)Check object type

    πŸŽ‰ Great work! You've completed this lesson.

    You now understand classes, objects, attributes, methods, and the four OOP pillars. These concepts power virtually every Python framework and library.

    Up next: Inheritance and Polymorphism β€” learn to build class hierarchies and reuse code elegantly.

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy Policy β€’ Terms of Service