Keep the heroic fight with fonts for another day.
Here s the same guide in plain
English.
The 10 second idea
- **Class**: a blueprint that says what something has and what it can do.
- **Object (instance)**: a real thing built from that blueprint.
If a class is a recipe, an object is the cake. Programmers love cake metaphors
because sugar helps with debugging.
---
Core basics
1) Define a class and make objects
class Dog:
def __init__(self, name, age): # runs when you create a Dog
self.name = name # instance attribute
self.age = age
def bark(self): # instance method
return f"{self.name} says woof!"
d1 = Dog("Momo", 3) # make an object
print(d1.bark()) # "Momo says woof!"
- `__init__` sets up each new object.
- `self` means this object.
2) Attribute vs method
- **Attribute**: data on the object, e.g. `d1.name`
- **Method**: function on the object, e.g. `d1.bark()`
3) Class attribute vs instance attribute
class Car:
wheels = 4 # class attribute (shared)
def __init__(self, model):
self.model = model # instance attribute (per object)
a = Car("Civic")
b = Car("Model 3")
Car.wheels # 4
a.wheels # 4
a.model # "Civic" (b is unchanged)
Changing `Car.wheels` affects all cars; changing `a.model` affects only `a`.
4) instance / classmethod / staticmethod
class Temperature:
def __init__(self, celsius):
self.c = celsius
def in_f(self): # instance: needs self
return self.c * 9/5 + 32
@classmethod
def from_f(cls, f): # gets the class
return cls((f - 32) * 5/9)
@staticmethod
def is_valid(t): # helper; no self/cls
return -273.15 <= t
- Use `@classmethod` for alternate constructors.
- Use `@staticmethod` for small helpers that live nicely inside the class.
5) Friendly printing: `__repr__` and `__str__`
class User:
def __init__(self, name): self.name = name
def __repr__(self): return f"User(name={self.name!r})" # for developers
def __str__(self): return self.name # for humans
print(User("Ava")) # "Ava"
User("Ava") # User(name='Ava')
6) Equality vs identity
u1 = User("Ava")
u2 = User("Ava")
u1 == u2 # False by default (unless you define __eq__)
u1 is u2 # False (different objects)
`==` means equal in value; `is` means the very same object.
7) Encapsulation (Python-style)
- `_name` means internal; handle with care.
- `__name` uses name mangling to avoid collisions in subclasses.
- Use `@property` for getter/setter behavior:
class Account:
def __init__(self, balance):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("No negative balance.")
self._balance = value
8) Inheritance vs composition
**Inheritance**: is a relationship.
class Animal:
def speak(self): return "noise"
class Cat(Animal):
def speak(self): return "meow" # override
**Composition**: has a relationship. Often cleaner.
class Engine:
def start(self): return "vroom"
class Vehicle:
def __init__(self):
self.engine = Engine() # has an Engine
9) `super()` in subclasses
class LoggedList(list):
def append(self, item):
print("adding", item)
super().append(item)
10) Dataclasses: quick data containers
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int = 0
p = Point(3) # Point(x=3, y=0)
p.x # 3
You get `__init__`, `__repr__`, and `__eq__` for free.
---
A small practical example
from dataclasses import dataclass, field
from datetime import datetime
from typing import List
@dataclass
class Task:
title: str
done: bool = False
created_at: datetime = field(default_factory=datetime.utcnow)
def mark_done(self):
self.done = True
class TodoList:
def __init__(self, owner):
self.owner = owner
self._tasks: List[Task] = []
def add(self, title: str):
self._tasks.append(Task(title))
def pending(self):
return [t for t in self._tasks if not t.done]
def completed(self):
return [t for t in self._tasks if t.done]
def __len__(self):
return len(self._tasks)
def __repr__(self):
return f"TodoList(owner={self.owner!r}, tasks={len(self)})"
todo = TodoList("Ravi")
todo.add("Buy milk")
todo.add("Write code")
todo.pending()[0].mark_done()
len(todo) # 2
todo.completed() # [Task(title='Buy milk', done=True, ...)]
repr(todo) # "TodoList(owner='Ravi', tasks=2)"
---
Common mistakes
- Forgetting `self` in method definitions.
- Using a mutable default:
# bad
def __init__(self, items=[]): ...
# good
def __init__(self, items=None):
self.items = [] if items is None else list(items)
- Mixing up class and instance attributes.
- Using `is` for equality. Use `==` unless you re checking identity.
- Skipping `__repr__` and then suffering during debugging.
---
Tiny cheat sheet
class X:
class_attr = 1
def __init__(self, a): self.a = a
def method(self): return self.a
@classmethod
def make(cls, a): return cls(a)
@staticmethod
def help(): return "useful fact"
@property
def doubled(self): return self.a * 2
def __repr__(self): return f"X(a={self.a!r})"
def __eq__(self, other): return isinstance(other, X) and self.a == other.a