1. Classes in Python
Python is an object-oriented programming language at its core. While functions allow you to group reusable actions together, Classes allow you to group both data (attributes) and behaviors (methods) into a single, cohesive unit. Mastering classes allows you to build sophisticated, structured applications like web frameworks, game engines, and data models.
Learning Objectives
- Understand the syntax for creating and instantiating custom Python classes.
- Distinguish clearly between Instance Attributes and Class Attributes.
- Master special "Dunder" (double underscore) methods like
__init__()and__str__(). - Implement custom instance methods to modify an object's internal state.
- Follow Pythonic conventions like PascalCase naming for class definitions.
2. Understanding Classes: The Architectural Blueprint
A Class serves as a blueprint or template for creating objects. An Instance (or Object) is the actual physical item built using that blueprint.
Imagine a blueprint for a Smartphone. The blueprint defines that every phone will have a brand, a model, and a battery_level (attributes), and that every phone can make_call() or charge() (methods). However, your personal iPhone and your friend's Samsung Galaxy are two distinct instances created from that concept, each holding its own battery percentage and unique data.
Python code example:
# Defining a class using PascalCase convention
class Smartphone:
# Class Attribute (shared across ALL instances)
category = "Mobile Electronics"
# Constructor method for setting Instance Attributes (unique per object)
def __init__(self, brand, model, battery_level=100):
self.brand = brand
self.model = model
self.battery_level = battery_level
# Instantiating (creating) two distinct objects
phone1 = Smartphone("Apple", "iPhone 15", 85)
phone2 = Smartphone("Samsung", "Galaxy S24", 92)
print(f"Phone 1: {phone1.brand} {phone1.model} ({phone1.battery_level}%)")
print(f"Phone 2: {phone2.brand} {phone2.model} ({phone2.battery_level}%)")
3. Instance Attributes vs. Class Attributes
Understanding where a variable lives inside a class is critical for avoiding unexpected bugs:
- Instance Attributes: Variables attached to
selfinside__init__(). Every single object gets its own independent copy of these values. - Class Attributes: Variables defined directly inside the class block (outside any method). They are shared across every instance of that class to conserve memory.
| Attribute Type | Where Defined | Scope & Ownership | Best Used For |
|---|---|---|---|
| Instance Attribute | Inside __init__() using self.attr |
Unique to each individual object. | Data that varies per object (e.g., username, balance, ID). |
| Class Attribute | Directly under class ClassName: |
Shared across all instances of the class. | Constants, global counters, or shared configurations. |
4. Special Dunder Methods: __init__ and __str__
Python classes feature special methods surrounded by double underscores, often referred to as Dunder Methods or magic methods:
1. The __init__() Method
The constructor method that Python calls automatically whenever a new instance is instantiated. It sets up the object's initial state.
2. The __str__() Method
Determines how the object is represented as a string when passed to print() or str(). Without __str__(), printing an object outputs an unhelpful memory address like <__main__.Smartphone object at 0x7f8a>.
Python code example:
class Laptop:
def __init__(self, brand, ram_gb):
self.brand = brand
self.ram_gb = ram_gb
# Custom string representation for human-readable output
def __str__(self):
return f"{self.brand} Laptop with {self.ram_gb}GB RAM"
my_laptop = Laptop("Dell", 16)
# Automatically triggers __str__()!
print(my_laptop) # Output: Dell Laptop with 16GB RAM
5. Adding Custom Instance Methods
Methods are functions defined inside a class that operate on the instance's data. They can read or modify self attributes to update the object's state over time.
Python code example:
class BankAccount:
def __init__(self, account_holder, balance=0.0):
self.account_holder = account_holder
self.balance = balance
def deposit(self, amount):
if amount > 0:
self.balance += amount
print(f"Deposited ${amount:.2f}. New Balance: ${self.balance:.2f}")
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
print(f"Withdrew ${amount:.2f}. Remaining Balance: ${self.balance:.2f}")
else:
print("Transaction declined: Insufficient funds or invalid amount.")
# Usage
account = BankAccount("Alex", 150.00)
account.deposit(50.00) # Deposited $50.00. New Balance: $200.00
account.withdraw(75.00) # Withdrew $75.00. Remaining Balance: $125.00
6. Real World Example: RPG Video Game Character
Classes shine when building game characters, UI elements, or database entities where each entity needs independent state tracking:
class Hero:
# Class attribute to track total created heroes
total_heroes = 0
def __init__(self, name, hero_class, health=100):
self.name = name
self.hero_class = hero_class
self.health = health
self.inventory = []
Hero.total_heroes += 1 # Increment global class counter
def take_damage(self, damage):
self.health = max(0, self.health - damage)
print(f"🛡️ {self.name} took {damage} damage! Health is now {self.health}.")
def heal(self, amount):
self.health += amount
print(f"✨ {self.name} healed for {amount} HP! Health is now {self.health}.")
def add_item(self, item_name):
self.inventory.append(item_name)
print(f"🎒 {self.name} found: {item_name}")
# Simulating gameplay
knight = Hero("Arthur", "Paladin", health=120)
mage = Hero("Merlin", "Wizard", health=80)
knight.take_damage(30)
knight.add_item("Excalibur")
mage.heal(15)
print(f"Total active heroes in world: {Hero.total_heroes}")
7. Common Beginner Mistakes
⚠️ Watch out for these mistakes:
- Forgetting
selfin Method Declarations: Writingdef show_details():inside a class raisesTypeError: show_details() takes 0 positional arguments but 1 was givenwhen called on an instance. - Mutating Mutable Class Attributes: Defining a list as a class attribute (e.g.,
items = []outside__init__) shares that exact same list across ALL instances, leading to corrupted data! - Not Returning a String from
__str__(): The__str__()method must return a string. Returning an integer orNonewill raise aTypeError. - Not Using PascalCase for Class Names: Naming classes in lower_snake_case (e.g.,
class bank_account:) violates Python PEP 8 conventions. Useclass BankAccount:instead.
8. Code Examples
Here are production-ready patterns for defining and working with classes in Python:
# 1. Class tracking object counts with class attributes
class Employee:
emp_count = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.emp_count += 1
def __str__(self):
return f"Employee: {self.name} | Salary: ${self.salary:,}"
e1 = Employee("Sarah", 85000)
e2 = Employee("David", 92000)
print(e1)
print(f"Total Employees: {Employee.emp_count}")
# 2. Encapsulating state modifications cleanly
class Thermostat:
def __init__(self, temp_celsius=20.0):
self.temp_celsius = temp_celsius
def set_temp(self, new_temp):
if -10.0 <= new_temp <= 45.0:
self.temp_celsius = new_temp
else:
print("Temperature out of safe operating range!")
Mini Practice: Car Class Builder
Create a class named Car with an __init__() method that initializes make, model, and speed (default 0). Add an accelerate(amount) method that increases speed and a __str__() method that returns "Make Model running at Speed km/h".
Practice complete!
Fantastic! You've mastered writing custom classes, instance methods, and dunder methods in Python.
Test Your Understanding
1. What is the difference between a Class and an Instance (Object)?
2. What happens when you print an object that has a custom `__str__()` method defined?
3. What is the scope of a Class Attribute defined directly under `class Student:`?
4. According to PEP 8 naming conventions, how should Python classes be named?
9. Challenge Project
Student Grade Tracker: Build a Student class that tracks academic performance:
- Include attributes for
name,student_id, and a list ofgrades(starts empty). - Add an
add_grade(score)method that appends valid scores (0-100) to the list. - Add a
get_average()method that calculates and returns the student's average test score. - Implement a
__str__()method that displays the student name, ID, and average score rounded to 1 decimal place!
10. Key Takeaways
- Classes group state (attributes) and behavior (methods) into reusable blueprints.
- Instantiate objects by calling the class name like a function:
obj = ClassName(). - Use
__init__()to define instance attributes unique to each object. - Implement
__str__()to provide clean, human-readable string representations. - Instance attributes belong to specific objects; Class attributes are shared across all instances.
🚀 Progression
You've mastered Python Classes! Next up, we will explore code reuse and hierarchy with **Inheritance & Polymorphism in Python**!