Python Classes and Objects
Object-Oriented Programming (OOP) is a programming paradigm that organizes code into classes and objects. A class is a blueprint for creating objects, and an object is an instance of a class.
Defining a Class
python
class Dog:
# Class attribute (shared by all instances)
species = "Canis familiaris"
# Constructor (initializer)
def __init__(self, name, age):
# Instance attributes (unique to each instance)
self.name = name
self.age = age
# Instance method
def bark(self):
return f"{self.name} says Woof!"
# Another method
def info(self):
return f"{self.name} is {self.age} years old"Creating Objects
python
# Create instances (objects)
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
# Access attributes
print(dog1.name) # Buddy
print(dog2.age) # 5
print(dog1.species) # Canis familiaris
# Call methods
print(dog1.bark()) # Buddy says Woof!
print(dog2.info()) # Max is 5 years old
# Modify attributes
dog1.age = 4
print(dog1.info()) # Buddy is 4 years old
# Each object is independent
print(dog1 is dog2) # FalseThe __init__ Method
The constructor initializes new objects:
python
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
self.transactions = []
def deposit(self, amount):
if amount > 0:
self.balance += amount
self.transactions.append(f"+${amount:.2f}")
return True
return False
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
self.transactions.append(f"-${amount:.2f}")
return True
return False
def get_statement(self):
print(f"Account: {self.owner}")
print(f"Balance: ${self.balance:.2f}")
print(f"Transactions: {', '.join(self.transactions)}")
# Usage
account = BankAccount("Alice", 1000)
account.deposit(500)
account.withdraw(200)
account.get_statement()
# Account: Alice
# Balance: $1300.00
# Transactions: +$500.00, -$200.00The self Parameter
self refers to the current instance. It's automatically passed when calling methods:
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance_to(self, other):
"""Calculate distance to another point."""
dx = self.x - other.x
dy = self.y - other.y
return (dx**2 + dy**2) ** 0.5
p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance_to(p2)) # 5.0
# Equivalent explicit call (rarely used)
print(Point.distance_to(p1, p2)) # 5.0Class vs Instance Attributes
python
class Student:
# Class attribute - shared by ALL instances
school = "BigXStar Academy"
student_count = 0
def __init__(self, name):
# Instance attribute - unique to each instance
self.name = name
Student.student_count += 1
def __repr__(self):
return f"Student('{self.name}')"
s1 = Student("Alice")
s2 = Student("Bob")
# Class attribute - same for all
print(s1.school) # BigXStar Academy
print(s2.school) # BigXStar Academy
print(Student.student_count) # 2
# Modifying class attribute via class
Student.school = "Tech Academy"
print(s1.school) # Tech Academy (changed for all)
print(s2.school) # Tech Academy
# Modifying via instance creates a new instance attribute
s1.school = "Private School"
print(s1.school) # Private School (instance attribute)
print(s2.school) # Tech Academy (still class attribute)
print(Student.school) # Tech AcademySpecial (Magic/Dunder) Methods
python
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# String representation (for users)
def __str__(self):
return f"({self.x}, {self.y})"
# Developer representation
def __repr__(self):
return f"Vector({self.x}, {self.y})"
# Addition: v1 + v2
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
# Subtraction: v1 - v2
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
# Equality: v1 == v2
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# Length (magnitude)
def __abs__(self):
return (self.x**2 + self.y**2) ** 0.5
# Boolean (non-zero vector)
def __bool__(self):
return self.x != 0 or self.y != 0
# Length for len()
def __len__(self):
return 2 # 2D vector
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1) # (3, 4)
print(repr(v1)) # Vector(3, 4)
print(v1 + v2) # (4, 6)
print(v1 - v2) # (2, 2)
print(v1 == v2) # False
print(abs(v1)) # 5.0
print(bool(Vector(0, 0))) # FalseCommon Magic Methods
| Method | Operator/Function |
|---|---|
__init__ | Constructor |
__str__ | str(), print() |
__repr__ | repr(), debugging |
__add__ | + |
__sub__ | - |
__mul__ | * |
__eq__ | == |
__lt__ | < |
__len__ | len() |
__getitem__ | obj[key] |
__setitem__ | obj[key] = val |
__contains__ | in |
__iter__ | for x in obj |
__call__ | obj() |
Class Methods and Static Methods
python
class Employee:
raise_rate = 1.05 # 5% raise
def __init__(self, name, salary):
self.name = name
self.salary = salary
# Regular instance method
def apply_raise(self):
self.salary = int(self.salary * self.raise_rate)
# Class method - receives class as first argument
@classmethod
def set_raise_rate(cls, rate):
cls.raise_rate = rate
# Class method as alternative constructor
@classmethod
def from_string(cls, emp_str):
name, salary = emp_str.split("-")
return cls(name, int(salary))
# Static method - no access to instance or class
@staticmethod
def is_workday(day):
return day.weekday() < 5
# Class method
Employee.set_raise_rate(1.10)
# Alternative constructor
emp = Employee.from_string("Alice-75000")
print(f"{emp.name}: ${emp.salary}") # Alice: $75000
# Static method
import datetime
today = datetime.date.today()
print(Employee.is_workday(today))Properties
Control attribute access with getters and setters:
python
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
"""Get temperature in Celsius."""
return self._celsius
@celsius.setter
def celsius(self, value):
"""Set temperature with validation."""
if value < -273.15:
raise ValueError("Temperature below absolute zero!")
self._celsius = value
@property
def fahrenheit(self):
"""Get temperature in Fahrenheit."""
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5/9
# Usage
temp = Temperature(25)
print(temp.celsius) # 25
print(temp.fahrenheit) # 77.0
temp.fahrenheit = 212
print(temp.celsius) # 100.0
# Validation works
# temp.celsius = -300 # ValueError!Practical Example: Library System
python
"""
A simple library management system using classes.
"""
class Book:
def __init__(self, title, author, isbn):
self.title = title
self.author = author
self.isbn = isbn
self.is_available = True
def __str__(self):
status = "Available" if self.is_available else "Checked Out"
return f"'{self.title}' by {self.author} [{status}]"
def __repr__(self):
return f"Book('{self.title}', '{self.author}', '{self.isbn}')"
class Library:
def __init__(self, name):
self.name = name
self.books = []
def add_book(self, book):
self.books.append(book)
print(f"Added: {book.title}")
def find_book(self, title):
for book in self.books:
if book.title.lower() == title.lower():
return book
return None
def checkout(self, title):
book = self.find_book(title)
if book and book.is_available:
book.is_available = False
print(f"Checked out: {book.title}")
return True
print(f"Cannot checkout: {title}")
return False
def return_book(self, title):
book = self.find_book(title)
if book and not book.is_available:
book.is_available = True
print(f"Returned: {book.title}")
def display_catalog(self):
print(f"\nπ {self.name} Catalog:")
print("-" * 40)
for book in self.books:
print(f" {book}")
# Usage
library = Library("City Library")
library.add_book(Book("Python Crash Course", "Eric Matthes", "978-1"))
library.add_book(Book("Clean Code", "Robert Martin", "978-2"))
library.add_book(Book("The Pragmatic Programmer", "Hunt & Thomas", "978-3"))
library.display_catalog()
library.checkout("Clean Code")
library.display_catalog()
library.return_book("Clean Code")Summary
- A class is a blueprint; an object is an instance of a class
__init__is the constructor that initializes new objectsselfrefers to the current instance- Class attributes are shared by all instances; instance attributes are unique
- Magic methods (
__str__,__add__, etc.) enable operator overloading @classmethodreceives the class;@staticmethodreceives neither class nor instance@propertyprovides controlled access to attributes with getters/setters- OOP helps organize code into reusable, modular components
Next, we'll learn about inheritance in Python.