Introduction to Object-Oriented Programming
Real-world analogy: - A Car has properties (color, model, year) and behaviors (start, stop, accelerate) - Each specific car is an instance of the Car class
Class: - Blueprint or template - Defines structure and behavior - Like a house blueprint
Object: - Instance of a class - Has actual values - Like an actual house
Basic class syntax:
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
return f"{self.name} says Woof!"
def info(self):
return f"{self.name} is a {self.breed}"
# Creating an instance
my_dog = Dog("Buddy", "Golden Retriever")
print(my_dog.bark()) # Buddy says Woof!
print(my_dog.info()) # Buddy is a Golden Retriever__init__ MethodConstructor method: - Called automatically when creating an object - Sets up initial state of the object - self refers to the instance being created
class Person:
def __init__(self, name, age, email):
self.name = name # Instance attribute
self.age = age # Instance attribute
self.email = email # Instance attribute
self.friends = [] # Default attribute
# Creating instances
person1 = Person("Alice", 25, "alice@email.com")
person2 = Person("Bob", 30, "bob@email.com")
print(person1.name) # Alice
print(person2.age) # 30Attributes store data:
Methods define behavior:
def deposit(self, amount):
if amount > 0:
self.balance += amount
self.transaction_history.append(f"Deposited ${amount}")
return True
return False
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
self.transaction_history.append(f"Withdrew ${amount}")
return True
return False# Create account
account = BankAccount("123456789", 1000)
# Use methods
account.deposit(500)
account.withdraw(200)
# Access attributes
print(f"Account: {account.account_number}")
print(f"Balance: ${account.balance}")
print("History:", account.transaction_history)Output:
Account: 123456789
Balance: $1300
History: ['Deposited $500', 'Withdrew $200']
Class attributes are shared by all instances:
class Dog:
species = "Canis lupus" # Class attribute
total_dogs = 0 # Class attribute
def __init__(self, name, breed):
self.name = name # Instance attribute
self.breed = breed # Instance attribute
Dog.total_dogs += 1 # Increment class attribute
@classmethod
def get_total_dogs(cls):
return cls.total_dogs
# Usage
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Max", "German Shepherd")
print(Dog.species) # Canis lupus
print(Dog.get_total_dogs()) # 2class Counter:
count = 0 # Class attribute
def __init__(self, name):
self.name = name # Instance attribute
Counter.count += 1 # Modify class attribute
self.instance_count = 1 # Instance attribute
counter1 = Counter("First")
counter2 = Counter("Second")
print(f"Total counters: {Counter.count}") # 2
print(f"Counter1 name: {counter1.name}") # First
print(f"Counter2 name: {counter2.name}") # SecondMake your objects printable:
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self):
return f"'{self.title}' by {self.author}"
def __repr__(self):
return f"Book('{self.title}', '{self.author}', {self.pages})"
book = Book("1984", "George Orwell", 328)
print(str(book)) # '1984' by George Orwell
print(repr(book)) # Book('1984', 'George Orwell', 328)
print(book) # Uses __str__ methodControl access to attributes:
class Circle:
def __init__(self, radius):
self._radius = radius # "Private" attribute
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
circle = Circle(5)
print(circle.area) # 78.53975
circle.radius = 10 # Uses setter
print(circle.area) # 314.159Python conventions for โprivateโ attributes:
class BankAccount:
def __init__(self, balance):
self.public_attr = "Anyone can access"
self._protected_attr = "Should not access directly"
self.__private_attr = "Name mangled for privacy"
def get_private(self):
return self.__private_attr
account = BankAccount(1000)
print(account.public_attr) # OK
print(account._protected_attr) # Works but not recommended
# print(account.__private_attr) # AttributeError
print(account.get_private()) # Access through methodNote: Python doesnโt have true private attributes, only conventions
Class methods work with the class, not instances:
class Person:
population = 0
def __init__(self, name):
self.name = name
Person.population += 1
@classmethod
def get_population(cls):
return cls.population
@classmethod
def create_anonymous(cls):
return cls("Anonymous")
print(Person.get_population()) # 0
person1 = Person("Alice")
anonymous = Person.create_anonymous()
print(Person.get_population()) # 2Static methods donโt use self or cls:
class MathUtils:
@staticmethod
def add(a, b):
return a + b
@staticmethod
def is_even(number):
return number % 2 == 0
@staticmethod
def factorial(n):
if n <= 1:
return 1
return n * MathUtils.factorial(n - 1)
# Can call without creating instance
print(MathUtils.add(5, 3)) # 8
print(MathUtils.is_even(4)) # True
print(MathUtils.factorial(5)) # 120class Student:
total_students = 0
def __init__(self, name, student_id, email):
self.name = name
self.student_id = student_id
self.email = email
self.grades = {}
self.enrolled_courses = []
Student.total_students += 1
def enroll_course(self, course):
if course not in self.enrolled_courses:
self.enrolled_courses.append(course)
self.grades[course] = []
def add_grade(self, course, grade):
if course in self.grades:
self.grades[course].append(grade)
def get_average(self, course):
if course in self.grades and self.grades[course]:
return sum(self.grades[course]) / len(self.grades[course])
return 0
def __str__(self):
return f"Student: {self.name} (ID: {self.student_id})"# Create students
alice = Student("Alice Johnson", "S001", "alice@school.edu")
bob = Student("Bob Smith", "S002", "bob@school.edu")
# Enroll in courses
alice.enroll_course("Math")
alice.enroll_course("Science")
bob.enroll_course("Math")
# Add grades
alice.add_grade("Math", 85)
alice.add_grade("Math", 92)
alice.add_grade("Science", 78)
# Check results
print(alice) # Student: Alice Johnson (ID: S001)
print(f"Math average: {alice.get_average('Math')}") # 88.5
print(f"Total students: {Student.total_students}") # 2Challenge 1: Rectangle Class
Rectangle Class:
Library Book System:
class LibraryBook:
def __init__(self, title, author, isbn):
self.title = title
self.author = author
self.isbn = isbn
self.is_checked_out = False
def check_out(self):
if not self.is_checked_out:
self.is_checked_out = True
return True
return False
def return_book(self):
if self.is_checked_out:
self.is_checked_out = False
return True
return FalseBankAccount not BA__init__ for initialization: Set up object state properly__str__ and __repr__1. Forgetting self:
Good candidates for classes: - Entities with both data and behavior - Complex state that needs to be maintained - When you need multiple instances - When inheritance would be beneficial
Consider alternatives: - Simple data storage โ use dictionaries or namedtuples - Pure functions โ donโt need classes - One-time use โ functions might be simpler
__init__ method initializes new instancesself to refer to the current instanceComing up next: - Inheritance and polymorphism - Advanced OOP concepts - Design patterns and best practices
Practice more: - Model real-world entities as classes - Create a simple game with classes - Build a basic inventory management system
Python Tutorial