Classes and Objects
Object-Oriented Programming (OOP) in Python allows you to define custom data types using classes. A class is a blueprint for creating objects, which are instances of the class.
Syntax
class ClassName: def __init__(self, attribute1, attribute2): self.attribute1 = attribute1 self.attribute2 = attribute2 def method(self): # Code for the method pass
Example
class Car: def __init__(self, brand, model): self.brand = brand self.model = model def drive(self): print(f"The {self.brand} {self.model} is driving.") car = Car("Toyota", "Corolla") car.drive() # Output: The Toyota Corolla is driving.
- The
__init__
method is a constructor that initializes the attributes of the class. Methods are functions defined inside the class that operate on its attributes.
Instance and Class Variables
Instance variables are unique to each object instance, while class variables are shared among all instances of a class.
Example
class Car: wheels = 4 # Class variable def __init__(self, brand, model): self.brand = brand # Instance variable self.model = model # Instance variable car1 = Car("Toyota", "Corolla") car2 = Car("Honda", "Civic") print(car1.wheels) # Output: 4 print(car2.wheels) # Output: 4
wheels
is a class variable, so its value is shared by all instances of theCar
class.brand
andmodel
are instance variables, unique to eachCar
object.
Pillars of OOP
Python supports the four main pillars of Object-Oriented Programming:
Encapsulation
Bundling the data (attributes) and methods (functions) that operate on the data into a single unit, or class.
class Account: def __init__(self, balance): # Private attribute (encapsulated) self.__balance = balance def withdraw(self, amount): try: # Check if there's enough balance if amount > self.__balance: raise ValueError("Insufficient funds") # Deduct amount if valid self.__balance -= amount except ValueError as e: # Handle insufficient funds print("Error:", e) else: # Runs only if no exception occurs print(f"Withdrew: ${amount}") finally: # Always runs — show current balance print(f"Balance: ${self.__balance}") # Create an account with $100 a = Account(100) # First withdrawal: should succeed a.withdraw(30) # Second withdrawal: should fail due to insufficient funds a.withdraw(100)
Example: demonstrates encapsulation by using a private attribute __balance
inside an Account
class. It also uses try
, except
, else
, and finally
blocks to safely handle withdrawals and exceptions like insufficient funds. The finally
block ensures the balance is always printed, regardless of success or failure.
Abstraction
Hiding the complex implementation details and exposing only the essential features of an object.
from abc import ABC, abstractmethod # Abstract class class Vehicle(ABC): @abstractmethod def drive(self): pass # Concrete class class Car(Vehicle): def drive(self): print("Car is driving...") # Using abstraction my_car = Car() my_car.drive() # You don't need to know how it works internally
Example: When you call the drive()
method on a Car
object, you don’t need to know how the method works internally.
Inheritance
Creating a new class that is a modified version of an existing class. The new class inherits attributes and methods from the parent class.
Example:
class ElectricCar(Car): def __init__(self, brand, model, battery_size): super().__init__(brand, model) self.battery_size = battery_size def charge(self): print(f"The {self.brand} {self.model} is charging.")
Polymorphism
The ability to present the same interface for different underlying data types or classes.
Example:
class Dog: def sound(self): return "Woof!” class Cat: def sound(self): return "Meow!" def make_sound(animal): print(animal.sound()) dog = Dog() cat = Cat() make_sound(dog) # Output: Woof! make_sound(cat) # Output: Meow!