This guide introduces encapsulation, the foundational concept of object-oriented programming (OOP). You’ll learn how to organize code by grouping related data and behavior together.
Encapsulation means bundling data and the code that works on that data together in a class. Instead of passing variables around to separate functions, the data lives alongside the functions that operate on it.
Without encapsulation, you end up passing the same data to many different functions:
# Without encapsulation - data and behavior are separate
from datetime import datetime
def format_user(user_id, email, name, created_at, activated_at):
return f"{name} ({email})"
def is_activated(user_id, email, name, created_at, activated_at):
return activated_at is not None
def user_to_dict(user_id, email, name, created_at, activated_at):
result = {"id": user_id, "email": email, "name": name}
if created_at:
result["created_at"] = created_at
if activated_at:
result["activated_at"] = activated_at
return result
# Every function needs all the user data
user_id = "user_abc123"
email = "student@example.com"
name = "Jane Doe"
created_at = datetime.now()
activated_at = None
print(is_activated(user_id, email, name, created_at, activated_at))
print(user_to_dict(user_id, email, name, created_at, activated_at))
This becomes messy. Adding a new field means updating every function.
A class bundles data and behavior together:
# With encapsulation - data and behavior are in one place
from datetime import datetime
class User:
"""A user in the system."""
def __init__(self, *, id=None, email=None, name=None, activated_at=None):
self.id = id if id is not None else generate_random_id()
self.email = email
self.name = name
self.created_at = datetime.utcnow()
self.activated_at = activated_at
def is_activated(self):
"""Check if this user has been activated."""
return self.activated_at is not None
def to_dict(self):
"""Convert user to a dictionary."""
result = {
"id": self.id,
"email": self.email,
"name": self.name,
"created_at": self.created_at
}
if self.activated_at:
result["activated_at"] = self.activated_at
return result
# Now we create a user once and use its methods
user = User(
id="user_abc123",
email="student@example.com",
name="Jane Doe"
)
print(user.is_activated()) # False
print(user.to_dict()) # {'id': 'user_abc123', 'email': ..., ...}
Much cleaner! The User class owns its data and the functions that use it.
In the examples above, generate_random_id() creates a unique ID string:
def generate_random_id():
"""Generate a unique random ID."""
import random
import string
return "".join(random.choices(string.ascii_letters + string.digits, k=12))
user = User(...))# Class definition
class User:
def __init__(self, *, name=None):
self.name = name
# Creating objects (instances)
user1 = User(name="Alice")
user2 = User(name="Bob")
__init__ MethodThe __init__ method runs when you create a new object. It sets up the object’s initial state:
class User:
def __init__(self, *, name=None, email=None):
self.name = name # Store data in the object
self.email = email
selfself refers to the current object. It lets methods access the object’s data:
class User:
def __init__(self, *, name=None):
self.name = name
def greet(self):
# self.name is this object's name
return f"Hello, I'm {self.name}"
Methods are functions defined inside a class. They operate on the object’s data:
class User:
def __init__(self, *, name=None):
self.name = name
def greet(self):
return f"Hello, I'm {self.name}"
def update_name(self, new_name):
self.name = new_name
Here’s a more complete example showing how encapsulation keeps related code together:
from datetime import datetime
class Task:
"""A task in a task management system."""
def __init__(self, *, title=None, description=None, priority=None):
self.id = generate_random_id()
self.title = title
self.description = description
self.priority = priority or "medium"
self.created_at = datetime.utcnow()
self.completed = False
def complete(self):
"""Mark the task as completed."""
self.completed = True
self.completed_at = datetime.utcnow()
def is_overdue(self, due_date):
"""Check if the task is overdue."""
if self.completed:
return False
return datetime.utcnow() > due_date
def to_dict(self):
"""Convert task to a dictionary for storage."""
return {
"id": self.id,
"title": self.title,
"description": self.description,
"priority": self.priority,
"created_at": self.created_at,
"completed": self.completed
}
# Usage
task = Task(
title="Fix login bug",
description="Users cannot log in with Google",
priority="high"
)
print(task.to_dict())
task.complete()
print(task.is_overdue(due_date=datetime.utcnow()))
Once you’re comfortable with encapsulation, you can explore more advanced OOP concepts:
These topics are covered in the collaborator training materials.