Warming up the neural circuits...
By the end of this chapter you will:
Bundling data with the functions that operate on it is the foundation of organized code. Without classes, you'd pass data structures around and hope every function handles them correctly. With classes, the data and its behavior live together — making code easier to understand, test, and maintain.
class User:
def __init__(self, name, age):
self.name = name # instance attribute
self.age = age
def greet(self):
return f"Hello, I'm {self.name}, {self.age} years old"
# Create instance
user = User("Alice", 30)
print(user.name) # 'Alice'
print(user.greet()) # "Hello, I'm Alice, 30 years old"Key facts:
__init__ runs when you create an instance: User("Alice", 30)self is the instance being created — Python passes it automaticallyclass User:
species = "human" # class attribute (shared)
def __init__(self, name):
self.name = name # instance attribute (unique)
alice = User("Alice")
bob =
Warning: Mutable class attributes are shared:
class Bad:
items = [] # shared by ALL instances
def add(self, item):
self.items.append(item)
a = Bad()
b = Bad()
a.add("x
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.
Key: @property lets you call a method without parentheses — it looks like an .
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str = ""
# Auto-generated __init__
user = User("Alice", 30)
# Auto-generated __repr__
Key: @dataclass generates __init__, __repr__, __eq__, and more for free. Use it when a class is mostly data.
Think of classes as blueprints:
| Mistake | Why it's wrong | Fix |
|---|---|---|
Forgetting self in method | TypeError: method() takes 0 positional arguments but 1 was given | Always include self as first parameter |
self.name = name in class body | Creates class attribute, not instance | Put in __init__ |
Mutable class attribute items = [] | Shared across all instances | Initialize in __init__ |
@property without setter | Can't set the property | Add @property_name.setter |
class Bad: items = []; def add(self, x): self.items.append(x)Beginner:
"What is self in Python classes?"
selfis the instance being created or modified. Python passes it automatically when you call a method. It's how methods access the instance's attributes.
"What's the difference between a class attribute and an instance attribute?"
Class attributes are shared by all instances (defined in the class body). Instance attributes are unique per instance (defined in
__init__). Class attributes are useful for constants; instance attributes for per-object data.
Senior:
"When would you use @dataclass vs a regular class?"
Use @dataclass when the class is mostly data (holds attributes, needs init, repr, eq). Use a regular class when you need custom behavior, complex initialization, or inheritance. @dataclass reduces boilerplate significantly.
"What's the difference between @property and a regular method?"
@property lets you call a method without parentheses — it looks like an attribute. Use it for computed values that don't take parameters. Regular methods require parentheses and can take arguments.
class Name: defines a class. __init__ is the constructor. self is the instance.@property makes methods look like attributes. @dataclass generates boilerplate.self as the first parameter in methods.I want to…
├── Define class → class Name:
├── Constructor → def __init__(self, ...):
├── Instance method → def method(self):
├── Class attribute → class_attr = value (in class body)
├── Instance attribute → self.attr = value (in __init__)
├── Property → @property
└── Dataclass → @dataclassWhat is 'self' in Python classes?