A class is a blueprint. An object is a real thing built from that blueprint. One "Dog" blueprint can make many dogs — each with its own name, but all able to bark.
This is Object-Oriented Programming (OOP). It lets you bundle data (a dog's name) and behavior (barking) into one neat package. We'll go in two stages: Part A — Basics, then Part B — Advanced.
🟢 Part A — The Basics
1️⃣ Class vs. Object
A class is like a house plan. An object (also called an instance) is an actual house built from it. From one plan you can build a whole street of houses.
class Dog: # the blueprint
pass
rex = Dog() # an object built from the blueprint
fido = Dog() # another, separate object
print(rex) # <__main__.Dog object ...>
2️⃣ The __init__ constructor
__init__ runs automatically the moment you create an object. Use it to set up the starting data each object needs.
class Dog:
def __init__(self, name, age):
self.name = name # store the data
self.age = age
rex = Dog("Rex", 3)
print(rex.name) # Rex
print(rex.age) # 3
3️⃣ The self parameter
self means "this particular object." It keeps each object's data tied to itself, so Rex's name never gets mixed up with Fido's.
💡 Two dogs, two names
rex.name is Rex's, fido.name is Fido's. self is how Python knows which dog you mean.
4️⃣ Attributes vs. Methods
Attributes = the object's data (variables), like self.name.
Methods = the object's actions (functions), like bark().
class Dog:
def __init__(self, name):
self.name = name # attribute (data)
def bark(self): # method (action)
return self.name + " says Woof!"
rex = Dog("Rex")
print(rex.bark()) # Rex says Woof!
🔴 Part B — Advanced
5️⃣ Instance vs. Class variables
An instance variable belongs to one object. A class variable is shared by all objects of that class.
class Dog:
species = "Canis familiaris" # class variable (shared)
def __init__(self, name):
self.name = name # instance variable (unique)
print(Dog("Rex").species) # Canis familiaris
print(Dog("Fido").species) # same for everyone
6️⃣ @classmethod vs. @staticmethod
@classmethod gets the class as cls — great for building objects in a special way.
@staticmethod is just a helper that lives in the class but needs no object.
class Dog:
def __init__(self, name): self.name = name
@classmethod
def puppy(cls): return cls("Baby") # makes a Dog
@staticmethod
def is_dog(sound): return sound == "Woof"
print(Dog.puppy().name) # Baby
print(Dog.is_dog("Woof")) # True
7️⃣ Getters & setters with @property
A property lets you guard an attribute — validate or compute it — while still using simple dog.age syntax.
class Dog:
def __init__(self, age): self._age = age
@property
def age(self): return self._age
@age.setter
def age(self, value):
if value < 0: raise ValueError("Age can't be negative")
self._age = value
d = Dog(3)
d.age = 5 # uses the setter (validates!)
print(d.age) # 5
8️⃣ Inheritance, overriding & super()
A child class inherits a parent's powers, can override a method to change it, and can call the parent with super().
class Animal:
def speak(self): return "..."
class Cat(Animal):
def speak(self): return "Meow" # override
class Puppy(Animal):
def speak(self): return super().speak() + " Woof"
print(Cat().speak()) # Meow
print(Puppy().speak()) # ... Woof
9️⃣ Polymorphism
Different classes can answer the same method call their own way. One loop, many behaviors.
animals = [Cat(), Puppy()]
for a in animals:
print(a.speak()) # each class responds its own way
🔟 Dunder methods & operator overloading
"Dunder" = double underscore. They make your objects behave like built-in types. __str__ controls printing; __add__ makes + work.
@dataclass auto-writes __init__ and __repr__ for data-heavy classes.
Abstract Base Classes force children to implement certain methods.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
print(Point(1, 2)) # Point(x=1, y=2)
✅ What you learned
Basics: class vs. object, __init__, self, attributes & methods.
Advanced: class vs. instance variables, @classmethod/@staticmethod, @property, inheritance, super(), polymorphism, dunder methods, dataclasses & ABCs.
🎮 Time to Practice!
Build blueprints, then unlock the advanced powers. 📦
🐶
Task 1: Make a Dog Bark
Easy
Write a Dog class with __init__(self, name) and a bark() method returning name + " says Woof!". Create Dog("Rex") and print its bark → Rex says Woof!.
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
return self.name + " says Woof!"
print(Dog("Rex").bark())
🏦
Task 2: A Safe Bank Account
Medium
Write a BankAccount starting at balance 0 with a deposit method that only adds when the amount is positive. Deposit 10, then -99 (ignored), then 20, and print the balance → 30.
Use @dataclass to make a Point with x and y — no __init__ needed! Print Point(1, 2) → Point(x=1, y=2).
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
print(Point(1, 2))
🏋️ Extra Practice — Engineering Reps
Objects are how big programs stay organized. Model a few little worlds of your own below.
🖨️
Task 8: Print an Object Nicely
Medium
Add a __str__ method to a Book class so print(book) shows 'Hobbit' by Tolkien (1937) instead of a memory address. Create one book and print it.
'Hobbit' by Tolkien (1937)
class Book:
def __init__(self, title, author, year):
self.title = title
self.author = author
self.year = year
def __str__(self):
return f"'{self.title}' by {self.author} ({self.year})"
print(Book("Hobbit", "Tolkien", 1937))
🔒
Task 9: Protect the Data
Medium
Write a Thermostat whose temperature can only be set between 5 and 30. Keep the value in self._temperature and provide set_temperature(value) that ignores silly values, plus get_temperature(). Try 20, then 99, then print the temperature — it should still be 20.
20
class Thermostat:
def __init__(self):
self._temperature = 18
def set_temperature(self, value):
if 5 <= value <= 30:
self._temperature = value
def get_temperature(self):
return self._temperature
t = Thermostat()
t.set_temperature(20)
t.set_temperature(99)
print(t.get_temperature())
🏭
Task 10: Class Methods and Counters
Tricky
Give Robot a class variable count that goes up every time a robot is built, and a @classmethod how_many(cls) that returns it. Build three robots and print how many exist.
A car is not a kind of engine — it has one. Write an Engine class with start() returning "Vroom", and a Car that creates an Engine inside __init__ and has drive() returning "Tesla: Vroom". Print Car("Tesla").drive().
Write Circle, Square and Triangle classes that each have an area() method. Put one of each in a list (radius 2, side 3, base 4 height 5) and loop through printing ClassName: area rounded to 2 decimals. The loop should not care which shape it holds — that is polymorphism.
Circle: 12.57
Square: 9.00
Triangle: 10.00
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
class Square:
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
class Triangle:
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return self.base * self.height / 2
for shape in [Circle(2), Square(3), Triangle(4, 5)]:
print(f"{type(shape).__name__}: {shape.area():.2f}")
📚
Task 13: Boss Level: Library System
Boss
Build a tiny library. Book holds a title and an is_borrowed flag. Library holds a list of books with add(book), borrow(title) (returns a message and marks it borrowed, refusing if it is already out) and available() returning the list of free titles. Add two books, borrow one twice, then print the available titles.
You borrowed Hobbit
Sorry, Hobbit is already out
['Matilda']
class Book:
def __init__(self, title):
self.title = title
self.is_borrowed = False
class Library:
def __init__(self):
self.books = []
def add(self, book):
self.books.append(book)
def borrow(self, title):
for book in self.books:
if book.title == title:
if book.is_borrowed:
return f"Sorry, {title} is already out"
book.is_borrowed = True
return f"You borrowed {title}"
return f"We do not have {title}"
def available(self):
return [b.title for b in self.books if not b.is_borrowed]
library = Library()
library.add(Book("Hobbit"))
library.add(Book("Matilda"))
print(library.borrow("Hobbit"))
print(library.borrow("Hobbit"))
print(library.available())