Python tutorials  /  Introduction to Object-Oriented Programming (OOP) in Python
Chapter 12 · Python

Introduction to Object-Oriented Programming (OOP) in Python

Object-Oriented Programming (OOP) is a programming paradigm that uses objects and classes to structure code in a more modular, reusable, and organized manner. It's based on the concept of 'objects', which contain both data (attributes) and behavior (methods). OOP emphasizes organizing code into self-contained units that represent real-world entities, making complex software systems easier to design, maintain, and scale.

Think of OOP like building with LEGO blocks. Instead of writing one massive program, you create small, self-contained blocks (called classes) that represent things in the real world. Each block has properties (like color and size) and can perform actions (like connecting to other blocks). You can reuse these blocks, combine them, and modify them without affecting the entire structure. This makes your code more organized, easier to understand, and simpler to maintain.

Consider a bank management system. In OOP, you would create a 'BankAccount' class representing a bank account with properties like account_holder, balance, and account_number. The class would have methods like deposit(), withdraw(), and check_balance(). Each customer's account becomes an object (instance) of this class. If you want to add a SavingsAccount or CheckingAccount, you can create new classes that inherit from BankAccount, reusing common functionality while adding specialized features. Without OOP, you'd write repetitive code for each account type, making the system difficult to maintain and prone to errors.

OOP solves several critical software development challenges: (1) Modularity - code is organized into logical units making it easier to understand and modify. (2) Reusability - classes can be reused across projects, saving development time. (3) Maintainability - changes are localized to specific classes, reducing side effects. (4) Scalability - large systems are easier to manage when built from well-designed objects. (5) Real-world mapping - objects mirror real-world entities, making code intuitive. (6) Team collaboration - multiple developers can work on different classes independently. (7) Security - encapsulation hides internal details, controlling what can be accessed.

  • Class-Based OOP: The most common OOP paradigm where you define blueprints (classes) and create instances (objects) from them. Python uses class-based OOP.
  • Prototype-Based OOP: Used in languages like JavaScript where objects are created directly without explicit class definitions.
  • Functional OOP: Combines OOP with functional programming paradigms, emphasizing immutability and pure functions.
class ClassName: # Class attributes class_variable = value # Constructor method def __init__(self, parameters): # Instance attributes self.instance_variable = value # Instance methods def method_name(self, parameters): # Method body pass # Class method @classmethod def class_method_name(cls): pass # Static method @staticmethod def static_method_name(): pass # Creating an object (instance) object = ClassName(arguments) # Accessing attributes and methods object.instance_variable object.method_name()
Consider building a system to manage different types of vehicles (cars, bikes, trucks) in a transportation company. Without OOP, you'd write separate functions for each vehicle type with duplicated code for common operations like starting engines and calculating fuel consumption. This creates maintenance nightmares - fixing a bug in fuel calculation requires updating multiple functions. With OOP, you can create a Vehicle class with common functionality and let specific vehicle types inherit and extend it, reducing code duplication and making updates simpler.
Basic Class and Object Creation
Creating a simple class with attributes and methods, then instantiating objects from it.
python
class Dog: # Constructor def __init__(self, name, age): self.name = name self.age = age # Instance method def bark(self): return f"{self.name} says Woof!" def get_age(self): return f"{self.name} is {self.age} years old" # Creating objects (instances) dog1 = Dog("Buddy", 5) dog2 = Dog("Max", 3) print(dog1.bark()) print(dog1.get_age()) print(dog2.bark())
Buddy says Woof! Buddy is 5 years old Max says Woof!
We define a Dog class with __init__ (constructor) that initializes name and age attributes. The bark() and get_age() methods operate on instance data. We create two dog objects with different values, demonstrating how a single class blueprint creates multiple distinct objects with their own data.
Class and Instance Variables
Understanding the difference between class-level and instance-level variables.
python
class Car: # Class variable (shared by all instances) wheels = 4 def __init__(self, brand, model): # Instance variables (unique to each instance) self.brand = brand self.model = model def display_info(self): return f"{self.brand} {self.model} has {Car.wheels} wheels" # Creating objects car1 = Car("Toyota", "Camry") car2 = Car("Honda", "Civic") print(car1.display_info()) print(car2.display_info()) print(f"Total wheels: {Car.wheels}")
Toyota Camry has 4 wheels Honda Civic has 4 wheels Total wheels: 4
The 'wheels' class variable is shared across all Car instances and remains constant. The 'brand' and 'model' are instance variables unique to each object. Class variables are accessed via the class name or instance, while instance variables are specific to each object.
Methods and Self Parameter
Understanding how methods work with the self parameter to access instance data.
python
class BankAccount: def __init__(self, account_holder, balance): self.account_holder = account_holder self.balance = balance def deposit(self, amount): if amount > 0: self.balance += amount return f"Deposited ${amount}. New balance: ${self.balance}" return "Deposit amount must be positive" def withdraw(self, amount): if amount > 0 and amount <= self.balance: self.balance -= amount return f"Withdrew ${amount}. New balance: ${self.balance}" return "Invalid withdrawal amount" def check_balance(self): return f"{self.account_holder}'s balance: ${self.balance}" # Creating and using objects account = BankAccount("John Doe", 1000) print(account.check_balance()) print(account.deposit(500)) print(account.withdraw(200))
John Doe's balance: $1000 Deposited $500. New balance: $1500 Withdrew $200. New balance: $1300
The 'self' parameter allows methods to access and modify instance variables. Each method call uses 'self' to reference the specific object's data. The deposit() and withdraw() methods modify the balance attribute of the calling object, demonstrating how OOP encapsulates data and behavior together.
Encapsulation with Private Variables
Using naming conventions to create private attributes that shouldn't be accessed directly.
python
class Student: def __init__(self, name, gpa): self.name = name self._gpa = gpa # Single underscore - weak privacy self.__student_id = None # Double underscore - name mangling def set_student_id(self, student_id): if len(str(student_id)) == 8: self.__student_id = student_id return "Student ID set successfully" return "Student ID must be 8 digits" def get_student_id(self): return self.__student_id def display_info(self): return f"Name: {self.name}, GPA: {self._gpa}, ID: {self.__student_id}" # Creating and using objects student = Student("Alice", 3.8) print(student.set_student_id(12345678)) print(student.display_info()) print(student.get_student_id())
Student ID set successfully Name: Alice, GPA: 3.8, ID: 12345678 12345678
Encapsulation protects internal data using naming conventions. The single underscore (_gpa) is a hint for weak privacy, while double underscore (__student_id) triggers name mangling, making it harder to access directly. The setter method (set_student_id) validates data before storing, maintaining data integrity.
  • Forgetting the self Parameter: A frequent error is forgetting to include 'self' as the first parameter in instance methods. Python won't know which object's data the method should operate on. For example: def bark(name): instead of def bark(self, name):. This causes an error when calling the method on an object because Python automatically passes the object as the first argument.
  • Modifying Class Variables Instead of Instance Variables: Confusing class variables with instance variables leads to unexpected behavior. If you set self.wheels = 5 thinking it's a class variable, you actually create an instance variable that shadows the class variable. Changes to instance variables don't affect the class variable or other instances, potentially causing logic errors.
  • Not Initializing Attributes in __init__: Creating attributes in methods other than __init__ makes code unpredictable. If a method tries to use an attribute that wasn't initialized in __init__, the object might not have that attribute yet, causing AttributeError. Always initialize all necessary attributes in the constructor.
  • Treating Encapsulation as Security: Python's private variables (double underscore) aren't truly private. Developers can still access them using name mangling (e.g., object._ClassName__private). Never rely on Python's naming conventions for security; use them for convention and to signal intent rather than enforce security.
  • Creating God Classes: Putting too much responsibility into a single class violates the Single Responsibility Principle. A class doing everything becomes hard to test, maintain, and modify. Break functionality into smaller, focused classes with single responsibilities.
  • Follow the Single Responsibility Principle: Each class should have one reason to change. A User class should handle user data, not database connections or email sending. Create separate classes for Database and EmailService. This makes classes focused, testable, and reusable.
  • Use Descriptive Names for Classes and Methods: Choose clear, meaningful names that describe the class's purpose. Use 'CustomerAccount' instead of 'Acc' and 'calculate_monthly_interest' instead of 'calc'. Good naming makes code self-documenting and easier to understand at first glance.
  • Initialize All Attributes in __init__: Define all instance attributes in the constructor. This makes the class's interface clear and prevents AttributeError at runtime. It also makes objects predictable - you know exactly what attributes an instance will have.
  • Use Properties for Controlled Access: Instead of direct attribute access, use @property decorators to create controlled getters and setters. This allows validation and computation while maintaining a clean interface: @property def age(self): return self._age. This enables adding logic later without changing the calling code.
  • Document Your Classes with Docstrings: Include docstrings describing the class's purpose, attributes, and methods. This helps other developers (and your future self) understand how to use the class correctly. Use the format: '''Class description. Attributes: list them. Methods: describe usage.'''
  • Keep Related Data Together: Group related attributes and methods in the same class. Don't scatter related functionality across multiple classes. This cohesion makes the code easier to understand and modify.
What is Object-Oriented Programming and why is it important?
OOP is a programming paradigm that organizes code into objects containing both data (attributes) and behavior (methods). It's important because it improves code organization, reusability, and maintainability. OOP allows developers to model real-world entities as objects, making complex systems easier to design and understand. It promotes modularity through encapsulation, enabling changes in one class without affecting others.
Easy
What is the difference between a class and an object?
A class is a blueprint or template that defines the structure and behavior of objects. It's an abstract concept defined in code. An object (or instance) is a concrete realization of a class created during program execution. For example, 'Dog' is a class, while 'Buddy' and 'Max' are objects (instances) of the Dog class. You can create multiple objects from a single class, each with their own data.
Easy
What is the purpose of the __init__ method in Python?
The __init__ method is the constructor that initializes a newly created object. It's automatically called when you create an instance of a class using ClassName(). The __init__ method typically sets up initial attribute values and performs any necessary setup. The 'self' parameter refers to the instance being created, allowing you to set instance-specific data.
Easy
Explain the difference between class variables and instance variables with an example.
Class variables are shared among all instances of a class and are defined at the class level. Instance variables are unique to each object and are defined in __init__. For example, in a Car class: 'wheels' is a class variable (all cars have 4 wheels), while 'color' and 'brand' are instance variables (different for each car). Class variables are accessed via ClassName.variable or self.variable, and changes affect all instances.
Medium
What is encapsulation and how is it implemented in Python?
Encapsulation is bundling data and methods together within a class and hiding internal implementation details. It controls what external code can access. Python implements encapsulation using naming conventions: single underscore (_variable) suggests weak privacy, double underscore (__variable) triggers name mangling for stronger privacy. However, Python relies on convention rather than strict enforcement. The benefits include protecting data integrity through validation methods and reducing coupling between classes.
Medium
What does the 'self' parameter represent in Python classes?
The 'self' parameter represents the instance of the class itself. It's the reference to the object on which a method is called. When you call object.method(), Python automatically passes 'object' as the 'self' parameter to the method. Using 'self', methods can access and modify the instance's attributes. It's a convention in Python; you could technically name it differently, but 'self' is the standard that all Python developers use.
Easy
How would you implement a method that validates data before storing it in an object?
You can use setter methods with validation logic. For example: def set_age(self, age): if 0 < age < 150: self._age = age else: raise ValueError('Invalid age'). Alternatively, use Python's @property decorator: @property def age(self): return self._age @age.setter def age(self, value): if 0 < value < 150: self._age = value. This validates data before assignment and maintains a clean interface.
Medium
Explain the concept of a method and how it differs from a function.
A method is a function defined inside a class that operates on instance data using 'self'. A function is standalone code not associated with any class. Methods have access to instance variables and other methods through 'self', allowing them to work with object state. Functions don't have this context. Methods promote encapsulation by bundling behavior with data, while functions are stateless utilities. In Python, you call methods on objects (object.method()) and functions directly (function()).
Medium
Create a Rectangle class with attributes length and width. Implement methods to calculate area and perimeter. Create two rectangle objects with different dimensions and display their properties.
class Rectangle: def __init__(self, length, width): self.length = length self.width = width def area(self): return self.length * self.width def perimeter(self): return 2 * (self.length + self.width) def display(self): return f"Rectangle: {self.length}x{self.width}, Area: {self.area()}, Perimeter: {self.perimeter()}" rect1 = Rectangle(5, 3) rect2 = Rectangle(10, 4) print(rect1.display()) print(rect2.display())
Easy
Create a Temperature class that stores temperature in Celsius. Implement methods to convert to Fahrenheit and Kelvin. Include validation to ensure temperature is physically possible.
class Temperature: def __init__(self, celsius): if celsius < -273.15: raise ValueError('Temperature cannot be below absolute zero') self.celsius = celsius def to_fahrenheit(self): return (self.celsius * 9/5) + 32 def to_kelvin(self): return self.celsius + 273.15 def display(self): return f"Celsius: {self.celsius}°C, Fahrenheit: {self.to_fahrenheit():.2f}°F, Kelvin: {self.to_kelvin():.2f}K" temp = Temperature(25) print(temp.display())
Medium
Create a Library class to manage books. Include methods to add books, remove books, search by title, and display all books. Each book should have title, author, and ISBN.
class Book: def __init__(self, title, author, isbn): self.title = title self.author = author self.isbn = isbn class Library: def __init__(self): self.books = [] def add_book(self, book): self.books.append(book) return f"Added: {book.title}" def remove_book(self, isbn): self.books = [b for b in self.books if b.isbn != isbn] return "Book removed" def search_by_title(self, title): return [b for b in self.books if b.title.lower() == title.lower()] def display_all(self): return [f"{b.title} by {b.author}" for b in self.books] library = Library() library.add_book(Book("Python Basics", "John Doe", "123")) library.add_book(Book("Web Dev", "Jane Smith", "456")) print(library.display_all())
Medium

Object-Oriented Programming is a powerful paradigm that transforms how we write software by organizing code into reusable, maintainable objects. In Python, OOP centers on classes - blueprints that define object structure - and instances - actual objects created from those blueprints. Key concepts include: (1) Classes and Objects - the foundation of OOP, (2) Attributes and Methods - data and behavior bundled together, (3) Encapsulation - hiding internal details and controlling access, (4) The 'self' parameter - representing the instance within methods. Python's syntax is clean and accessible, making OOP concepts easy to learn. Starting with simple classes and building complexity gradually helps solidify understanding. Mastering OOP fundamentals prepares you for inheritance, polymorphism, and abstraction - the remaining pillars of OOP covered in subsequent chapters.

© 2026 CompileX. Maintained by Aditya Kumar Sharma.