Skip to contentSkip to navigation
MODULE 09 · LESSON 01

Classes, Objects & Blueprints

Instantiate custom types, bind instance attributes, and define state-modifying behavior.

Core Learning Milestones:
Everything Is an Object
Classes are instances of type
Attribute Lookup
instance -> class -> MRO
Class vs Instance
Shared vs unique state
@property
Computed attributes
Estimated Time
30 Minutes
Comprehensive deep dive
Difficulty Level
Beginner
Opens Module 09
Knowledge Check
15 Questions
+35 XP Potential
01 / Foundations

A Class Is a Blueprint; an Object Is What Gets Built

class ServerConfig: pass defines a blueprint. config = ServerConfig() builds one concrete object from it — an instance. In Python, this isn't just a convenient mental model: classes themselves are objects too, instances of the built-in type metaclass.

Every object carries its own __dict__ — a namespace of the attributes that belong to it specifically — while the class carries a separate __dict__ of attributes and methods shared across every instance.

Just a bag of dicts?
config = {}
config['host'] = 'db1'
A class with identity and behavior
class ServerConfig:
  def __init__(self, host): ...
Think of it like this:

A class is like an architect's blueprint for a house: the blueprint itself isn't livable, but every house built from it (each instance) has its own address, its own furniture (instance attributes), while sharing the same floor plan (class attributes and methods).

Full Contract in Action

Inspecting Types and Class Objects

Run the snippet below to see how an instance, its class, and the metaclass all relate:

oop_types.py — Press 'Run Code' to execute in your browser
class ServerConfig:
    pass

# Creating an instance
config = ServerConfig()

print("Instance type:", type(config))
print("Class type (metaclass):", type(ServerConfig))
print("Is instance of ServerConfig:", isinstance(config, ServerConfig))
print("Is ServerConfig instance of type:", isinstance(ServerConfig, type))
Object Model Glossary: Core Concepts
typeMetaclass

Every class is an instance of type — type(MyClass) returns <class 'type'>.

__dict__Namespace

Both instances and classes carry their own __dict__ — the actual namespace attribute lookup searches.

isinstance()Check

Confirms an object belongs to a class (or any of its parents) without depending on how it was constructed.

@propertyComputed

Lets an attribute-style read (rect.area) actually run a method under the hood.

02 / Attribute Lookup Tracer

How obj.name Actually Resolves

Click through the three lookup steps Python performs for pool1.driver:

1. Check obj.__dict__
pool1.__dict__ # -> {'host': 'db1.internal', 'port': 5432}

Python first looks in the instance's own __dict__. If the attribute is found here, the search stops immediately — this is why setting pool1.driver = 'x' masks the class attribute.

03 / Class vs Instance

Shared State vs Per-Object State vs Dynamic Access

Every attribute falls into one of these categories — knowing which one avoids the classic shared-mutable-default trap.

Instance attributes

self.host = host

Assigned inside __init__ (or any method) — lives in that one object's __dict__, unique per instance.

pool1.driver = 'x'

Assigning directly on an instance creates a new instance attribute that shadows the class attribute of the same name.

Class attributes

driver = "asyncpg" # in class body

Shared by every instance of the class unless an instance attribute of the same name shadows it.

Dynamic access

getattr(obj, name, default)

Safe dynamic read — returns default instead of raising AttributeError if the name is missing.

setattr(obj, name, val)

Dynamic assignment — equivalent to obj.name = val, useful when the attribute name is a runtime string.

04 / Hands-on Examples

Worked Hands-on Walkthroughs

Progress step-by-step from inspecting types through class/instance namespaces to exposing computed state with @property:

Walkthrough 01 · FoundationLevel 1 · Foundation

Inspecting Types and Class Objects

Type Inspection
Problem Scenario

config is an instance of ServerConfig, and ServerConfig is an instance of type. Python's object model is fully unified.

Executable Solution
Inspecting Types and Class Objects
class ServerConfig:
    pass

# Creating an instance
config = ServerConfig()

print("Instance type:", type(config))
print("Class type (metaclass):", type(ServerConfig))
print("Is instance of ServerConfig:", isinstance(config, ServerConfig))
print("Is ServerConfig instance of type:", isinstance(ServerConfig, type))
Step-by-Step Explanation

config is an instance of ServerConfig, and ServerConfig is an instance of type. Python's object model is fully unified.

Walkthrough 02 · Building UpLevel 2 · Applied

Class vs Instance Attributes & Namespace Inspection

Namespace
Problem Scenario

Assigning pool1.driver creates an entry in pool1.__dict__, masking the class attribute for pool1 without mutating DatabasePool.driver for pool2.

Executable Solution
Class vs Instance Attributes & Namespace Inspection
class DatabasePool:
    driver = "asyncpg"  # Shared Class Attribute

    def __init__(self, host, port):
        self.host = host  # Instance Attribute
        self.port = port  # Instance Attribute

pool1 = DatabasePool("db1.internal", 5432)
pool2 = DatabasePool("db2.internal", 5432)

# Inspecting namespaces
print("pool1.__dict__:", pool1.__dict__)
print("DatabasePool.driver:", DatabasePool.driver)

# Overriding attribute on a single instance
pool1.driver = "psycopg2"  # Writes to pool1.__dict__!

print("pool1.driver:", pool1.driver)
print("pool2.driver:", pool2.driver)  # Unaffected!
print("pool1.__dict__ after override:", pool1.__dict__)
Step-by-Step Explanation

Assigning pool1.driver creates an entry in pool1.__dict__, masking the class attribute for pool1 without mutating DatabasePool.driver for pool2.

Walkthrough 03 · MasteryLevel 3 · Architecture

Exposing Computed State via @property

@property
Problem Scenario

rect.area is accessed without parentheses, yet evaluates dynamically whenever rect.width or rect.height change.

Executable Solution
Exposing Computed State via @property
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    @property
    def area(self):
        """Computed property: dynamically calculated when accessed."""
        return self.width * self.height

    @property
    def perimeter(self):
        return 2 * (self.width + self.height)

rect = Rectangle(10, 5)
print("Width:", rect.width)
print("Height:", rect.height)
# Accessed like a standard attribute without parentheses!
print("Area:", rect.area)
print("Perimeter:", rect.perimeter)

# Dynamic recalculation when dimensions change
rect.width = 20
print("Updated Area:", rect.area)
Step-by-Step Explanation

rect.area is accessed without parentheses, yet evaluates dynamically whenever rect.width or rect.height change.

05 / Traps to Avoid

Common Traps & How to Avoid Them

Study the mistakes and clean fixes below before designing your own classes:

!Trap 1: Syntax or Name Typos in Classes and Objects (OOP)
Common Trap
The Bug (Avoid)Buggy Code
# Mistyped variable or missing closing parenthesis result = classes_and_objects__oop__val
What Happens:
NameError: name is not defined
The Clean FixCorrected Code
# Declare and verify variables before execution val = 10 print("Defined correctly:", val)
Recommended Practice:
Never declare mutable defaults at the class body level; initialize per-instance state inside __init__.
Why It Happens

Beginners frequently encounter typos or forgotten definitions before using an identifier.

!Trap 2: Unintended State Mutation or Scope Leakage
Subtle Trap
The Bug (Avoid)Buggy Code
# Modifying shared state or unexpected variable shadowing def update_state(data=[]): data.append(1) return data
What Happens:
Default mutable argument accumulates values across separate function calls.
The Clean FixCorrected Code
def update_state(data=None): if data is None: data = [] data.append(1) return data
Recommended Practice:
Never declare mutable defaults at the class body level; initialize per-instance state inside __init__.
Why It Happens

Default arguments in Python are evaluated once at function definition time, not at invocation time.

!Trap 3: Resource Leaks & High-Scale Performance Regressions
Architecture Trap
The Bug (Avoid)Buggy Code
# Opening resources in a tight loop without closing or bounding memory files = [open(f"log_{i}.txt", "w") for i in range(5000)]
What Happens:
OSError: [Errno 24] Too many open files (file descriptor exhaustion)
The Clean FixCorrected Code
for i in range(5): with open(f"log_{i}.txt", "w") as f: f.write("Safe")
Recommended Practice:
Never declare mutable defaults at the class body level; initialize per-instance state inside __init__.
Why It Happens

Failing to release system resources leads to process termination under heavy production workloads.

06 / Real-World Systems

Real-World Systems in Action

See how scripts, microservices, and large distributed domains rely on well-modeled classes:

Developer ProductivityFoundation Script

Local Developer Automation Tool

SYSTEM #1
Organization Context

Individual developers and IT administrators automate everyday repetitive workflows.

Why This Pattern Works Here

A clean standalone script providing fast, local automation with zero dependencies.

Production Code Wrapper
Local Developer Automation Tool
class Robot:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return f"Greetings, I am {self.name}."

bot = Robot("Atlas")
print(bot.speak())
Cloud MicroservicesScaled Service · Applied

Backend API Service Integration

SYSTEM #2
Organization Context

SaaS platforms run backend microservices processing requests concurrently.

Why This Pattern Works Here

A modular service handler integrating validation and structured responses.

Production Code Wrapper
Backend API Service Integration
# Service integration layer
def handle_request(payload: dict) -> dict:
    return {"status": "success", "processed": True}
Distributed SystemsEnterprise · Architecture

High-Throughput Cloud Architecture

SYSTEM #3
Organization Context

Enterprise applications model complex domains (Users, Accounts, Subscriptions, Invoices) using cleanly encapsulated class models.

Why This Pattern Works Here

Enterprise applications model complex domains (Users, Accounts, Subscriptions, Invoices) using cleanly encapsulated class models.

Production Code Wrapper
High-Throughput Cloud Architecture
# High-resilience production pattern
import time

def process_stream(events: list) -> int:
    processed = 0
    for event in events:
        if event:
            processed += 1
    return processed
Key Takeaways

Key Points & Concept Review

  • Everything in Python is an object, and every class is an instance of the type metaclass.
  • Class attributes are defined in the class body and shared; instance attributes are bound to self and unique per object.
  • Python resolves obj.x by first inspecting obj.__dict__ before checking type(obj).__dict__ and parent classes.
  • Use getattr(obj, name, default) for safe, dynamic attribute retrieval and setattr(obj, name, val) for dynamic assignment.
  • Never declare mutable defaults (like [] or {}) at the class body level as they become shared mutable state across instances.
  • Use the @property decorator to define calculated or managed attributes accessed with standard dot syntax.
Study Guide & References

Frequently Asked Questions & Reference Hub

FAQ & References
Growth Mindset: OOP Clicks Once You Build a Few Classes

“Everything is an object” sounds abstract until you see it in the REPL — no worries if class-vs-instance attribute scoping feels confusing at first; the moment you accidentally share a mutable default, the distinction becomes permanent.

Related Built-in References Right Here
PyLabs Library Docs
Ready for Action

You've Mastered Classes & Objects!

You understand the object model, attribute lookup order, and computed properties. Now lock in your knowledge with hands-on practice.

Hands-on Code Challenge

Design a class with both shared and per-instance state.

+40 XP Reward

Earn experience points toward your developer rank as soon as you pass.

~3 Minutes to Complete

Bite-sized knowledge check with instant explanations for every question.

Worked example

Model a student once, then stamp out as many as needed — data (name, marks) plus behaviour (grade) live together.

Example
class Student:
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks

    def grade(self):
        return "A" if self.marks >= 80 else "B"

print(Student("Mim", 84).grade())

How it works, step by step

  1. __init__ runs automatically on creation, filling self.
  2. self.name / self.marks belong to THIS student.
  3. Methods read attributes through self — grade() checks this object's marks.

Your notes

Sign in to keep private notes alongside this lesson.

Sign in to take notes

Classes, Objects & Blueprints practice

Sign-in required

Create a Circle class. Its __init__ method should save a radius. Add a show_radius method that prints Radius: 4 for a circle with radius 4.

Coding Practice is Locked

Sign in to access the interactive Python editor, write and run code checks against automated tests, use hints, and earn XP.

Practice quiz

Check yourself

15 questions with explanations after submission, so you can correct course as you go.

Classes, Objects & Blueprints quiz

Locked

You must be signed in to take this quiz. Sign in to test your understanding, track your answers, and earn XP.

Lesson incomplete

Not completed

Create an ID or sign in to save lesson completion across devices.

Create ID to save
Module lesson progress0/6 complete
Module cheat sheet