Mingming Li — Click any blue heading below to expand the content.
A class is a blueprint for creating objects. It defines the properties (attributes) and behaviors (methods) that objects of that class will have. Object-Oriented Programming (OOP) helps organize code by modeling real-world entities.
# Defining a simple class
class Dog:
def __init__(self, name, age):
self.name = name # attribute
self.age = age # attribute
def bark(self): # method
print(f"{self.name} says: Woof!")
def get_info(self):
return f"{self.name} is {self.age} years old"
# Creating objects (instances) of the class
my_dog = Dog("Buddy", 3)
your_dog = Dog("Max", 5)
# Accessing attributes and methods
print(my_dog.name) # Buddy
print(my_dog.get_info()) # Buddy is 3 years old
my_dog.bark() # Buddy says: Woof!
__init__ is the constructor (called when creating an object). self refers to the current instance (like saying "this object").
The __init__ method runs automatically when you create a new object. It sets up the initial state of the object.
class Student:
def __init__(self, name, grade, student_id):
self.name = name
self.grade = grade
self.student_id = student_id
self.attendance = 0 # default value (not passed as parameter)
def record_attendance(self):
self.attendance += 1
print(f"{self.name} has attended {self.attendance} day(s)")
def display_info(self):
return f"ID: {self.student_id}, Name: {self.name}, Grade: {self.grade}"
# Creating objects
student1 = Student("Alice", "A", "S001")
student2 = Student("Bob", "B", "S002")
print(student1.display_info()) # ID: S001, Name: Alice, Grade: A
student1.record_attendance() # Alice has attended 1 day(s)
student1.record_attendance() # Alice has attended 2 day(s)
self.attendance = 0 is a default value. Not every attribute needs to come from a parameter!
Instance methods are functions inside a class that operate on the object's data. They always take self as the first parameter.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount > 0:
self.balance += amount
print(f"Deposited ${amount}. New balance: ${self.balance}")
else:
print("Deposit amount must be positive")
def withdraw(self, amount):
if amount > 0 and amount <= self.balance:
self.balance -= amount
print(f"Withdrew ${amount}. New balance: ${self.balance}")
else:
print("Insufficient funds or invalid amount")
def get_balance(self):
return f"{self.owner}'s balance: ${self.balance}"
# Using the class
account = BankAccount("Mingming", 100)
account.deposit(50) # Deposited $50. New balance: $150
account.withdraw(30) # Withdrew $30. New balance: $120
print(account.get_balance()) # Mingming's balance: $120
class Employee:
# Class attribute (shared by all instances)
company = "Tech Corp"
employee_count = 0
def __init__(self, name, salary):
# Instance attributes (unique to each instance)
self.name = name
self.salary = salary
Employee.employee_count += 1 # increment class attribute
def display_info(self):
return f"{self.name} works at {Employee.company}, salary: ${self.salary}"
# Creating instances
emp1 = Employee("Alice", 50000)
emp2 = Employee("Bob", 60000)
print(emp1.display_info()) # Alice works at Tech Corp, salary: $50000
print(emp2.display_info()) # Bob works at Tech Corp, salary: $60000
print(f"Total employees: {Employee.employee_count}") # Total employees: 2
# Modifying class attribute affects all instances
Employee.company = "New Tech Inc"
print(emp1.display_info()) # Alice works at New Tech Inc, salary: $50000
Inheritance lets a child class reuse code from a parent class. This is great for avoiding duplication!
# Parent class (base class)
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} makes a sound")
def move(self):
print(f"{self.name} moves")
# Child class (subclass) inherits from Animal
class Dog(Animal):
def speak(self): # Override parent method
print(f"{self.name} barks: Woof!")
def fetch(self): # New method specific to Dog
print(f"{self.name} fetches the ball")
class Cat(Animal):
def speak(self): # Override parent method
print(f"{self.name} meows: Meow!")
def climb(self): # New method specific to Cat
print(f"{self.name} climbs a tree")
# Using inheritance
animals = [Dog("Buddy"), Cat("Whiskers"), Animal("Generic")]
for animal in animals:
animal.speak()
animal.move()
print()
# Calling subclass-specific methods
buddy = Dog("Buddy")
buddy.fetch() # Buddy fetches the ball
super() lets you call methods from the parent class. This is useful when you want to extend (not replace) parent behavior.
class Vehicle:
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
def start(self):
print(f"{self.brand} {self.model} is starting")
def info(self):
return f"{self.year} {self.brand} {self.model}"
class Car(Vehicle):
def __init__(self, brand, model, year, doors):
super().__init__(brand, model, year) # Call parent constructor
self.doors = doors
def start(self):
super().start() # Call parent method
print("Vroom vroom! The car is ready to go")
def info(self):
return f"{super().info()} with {self.doors} doors"
# Using super()
car = Car("Toyota", "Camry", 2022, 4)
print(car.info()) # 2022 Toyota Camry with 4 doors
car.start() # Toyota Camry is starting
# Vroom vroom! The car is ready to go
Special methods (also called "dunder methods" because they have double underscores) let your objects work with built-in Python functions like print(), len(), and ==.
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self): # Called by print() and str()
return f"'{self.title}' by {self.author}"
def __repr__(self): # Representation for developers
return f"Book('{self.title}', '{self.author}', {self.pages})"
def __len__(self): # Called by len()
return self.pages
def __eq__(self, other): # Called by ==
if isinstance(other, Book):
return self.title == other.title and self.author == other.author
return False
def __lt__(self, other): # Called by < (for sorting)
return self.pages < other.pages
book1 = Book("Python Crash Course", "Eric Matthes", 544)
book2 = Book("Automate the Boring Stuff", "Al Sweigart", 592)
print(str(book1)) # 'Python Crash Course' by Eric Matthes
print(len(book1)) # 544
print(book1 < book2) # True (544 < 592)
books = [book2, book1]
books.sort() # Sorts by pages (using __lt__)
print(books[0]) # 'Python Crash Course' by Eric Matthes
__init__, __str__, __repr__, __len__, __eq__, __lt__, __add__, etc.
Private attributes (with double underscore __) cannot be accessed directly from outside the class. This protects sensitive data.
class BankAccount:
def __init__(self, account_number, initial_balance=0):
self.account_number = account_number
self.__balance = initial_balance # Private attribute (double underscore)
def get_balance(self):
"""Access private balance through a method"""
return self.__balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"Deposited ${amount}. New balance: ${self.__balance}")
else:
print("Deposit amount must be positive")
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"Withdrew ${amount}. New balance: ${self.__balance}")
else:
print("Insufficient funds or invalid amount")
@property
def balance(self):
return self.__balance
account = BankAccount("ACC123", 100)
# print(account.__balance) # Error! Cannot access private attribute directly
print(account.get_balance()) # 100
print(account.balance) # 100 (using property)
# DEFINING A CLASS
class ClassName:
class_attribute = "shared" # class attribute
def __init__(self, param1, param2): # constructor
self.instance_attr = param1 # instance attribute
self.__private = "secret" # private (cannot access outside)
def instance_method(self): # instance method
return self.instance_attr
# CREATING OBJECTS
obj = ClassName(value1, value2)
# INHERITANCE
class ChildClass(ParentClass):
def __init__(self, param1, param2, extra):
super().__init__(param1, param2) # call parent constructor
self.extra = extra
# COMMON SPECIAL METHODS
__init__(self) # constructor
__str__(self) # called by print()
__len__(self) # called by len()
__eq__(self, other) # called by ==
__lt__(self, other) # called by <
Look at this code from our game (Code-3 could use classes!). Imagine we create a Cactus class:
class Cactus:
def __init__(self, x, y, image):
self.x = x
self.y = y
self.image = image
self.rect = self.image.get_rect(midbottom=(x, y))
def move(self, speed):
self.x -= speed
self.rect.x = self.x
def draw(self, screen):
screen.blit(self.image, self.rect)
Questions:
self refer to?Cactusx, y, image, rect__init__ (constructor), move(), draw()self refers to the specific cactus object being created or usedTry these exercises to practice classes. Write your code in Thonny.
Rectangle with width and height. Add area() and perimeter() methods.Circle with radius. Add area() and circumference() (use 3.14 for pi).Person with name and age. Add greet() that prints "Hello, my name is [name]".BankAccount with owner and balance. Add deposit(), withdraw(), and display_balance().Student with name and grades list. Add add_grade() and average().Book. Override __str__() to return a formatted string.Vehicle parent class and Car and Bicycle child classes.Counter that counts how many objects have been created (use a class attribute).self as the first parameter of a method.__init__.print() statements inside methods to see what's happening. Use print(type(obj)) to check object types.
self represent?__init__ method called?self refers to the current instance of the class.__init__ is called automatically when you create a new object.__private_attribute__init__ constructor methodsuper() to call parent methods__str__ and __len__