Python tutorials  /  Classes and Objects in Python
Chapter 13 · Python

Classes and Objects in Python

A class is a blueprint or template in Python that defines a set of attributes (data) and methods (functions) that objects will have. An object (or instance) is a concrete realization of a class - a specific occurrence of the class with actual values for its attributes. Classes provide a way to bundle data and functionality together, while objects are the actual entities that use this bundled data and functionality. In Python, everything is an object, and most things are instances of some class.

Think of a class as a cookie cutter and objects as the actual cookies. The cookie cutter (class) defines the shape, size, and design. Each cookie you cut (object) is an instance of that design. The cookie cutter itself doesn't change - it's just a template. But each cookie can have different toppings or decorations (different attribute values). You can cut as many cookies as you want from the same cutter, and they're all individual objects with their own properties, even though they follow the same design.

Consider a car manufacturing system. A class is like the blueprint of a car model (e.g., 'Toyota Camry'). The blueprint specifies what attributes every Camry should have (engine type, transmission, color, number of doors) and what methods it can perform (start, accelerate, brake, turn). An object is an actual physical car produced from that blueprint - for instance, your neighbor's red Camry with license plate ABC123. Another object might be a blue Camry with license plate XYZ789. Both are objects of the Camry class, but they have different values for attributes like color and license plate. The class defines what a Camry is; objects are the actual cars on the road.

Classes and objects are fundamental because they: (1) Provide structure - organize related data and functions together, (2) Enable reusability - write a class once, create many objects from it, (3) Reduce redundancy - eliminate repetitive code through templates, (4) Model real-world entities - objects naturally represent things in the physical world, (5) Support teamwork - team members can work independently on different classes, (6) Improve debugging - errors are localized to specific objects or methods, (7) Enable scalability - as projects grow, organization through classes keeps them manageable, (8) Facilitate testing - individual classes can be tested in isolation.

  • Concrete Classes: Fully implemented classes that can be instantiated directly. They have complete implementations of all methods and can be used to create objects immediately.
  • Abstract Classes: Classes that cannot be instantiated directly but serve as templates for other classes. They define an interface that subclasses must implement.
  • Mutable Classes: Classes whose objects can have their attributes modified after creation. Most user-defined classes are mutable.
  • Immutable Classes: Classes whose objects cannot be changed after creation. Examples include strings, tuples, and namedtuples. Objects of immutable classes maintain the same state throughout their lifetime.
# Defining a class class ClassName: # Class variable (shared by all instances) class_variable = 'value' # Constructor (initialization method) def __init__(self, parameter1, parameter2): # Instance variables (unique to each object) self.attribute1 = parameter1 self.attribute2 = parameter2 # Instance method def method_name(self, parameter): # Method implementation return self.attribute1 + parameter # Special method (dunder method) def __str__(self): return f"ClassName({self.attribute1}, {self.attribute2})" # Creating an object (instantiation) object_name = ClassName('value1', 'value2') # Accessing attributes print(object_name.attribute1) # Calling methods object_name.method_name(parameter) # Checking instance type isinstance(object_name, ClassName) # Returns True
Imagine you're developing a student management system for a university. Without classes and objects, you'd need to write repetitive code for each student - separate variables for name, ID, email, GPA, and separate functions for operations like enrolling in courses, calculating GPA, and generating transcripts. This approach becomes unmaintainable as you add more students and operations. With classes and objects, you create a Student class that encapsulates all student data and operations. Each student becomes an object of this class, automatically inheriting all properties and capabilities. Adding new features or fixing bugs happens in one place, making the entire system easier to maintain and scale.
Simple Class Definition and Object Creation
Creating and using a basic class with attributes and methods.
python
# Define a class class Person: def __init__(self, name, age): self.name = name self.age = age def greet(self): return f"Hello, my name is {self.name}" def birthday(self): self.age += 1 return f"Happy birthday! {self.name} is now {self.age}" # Create objects (instances) person1 = Person("Alice", 30) person2 = Person("Bob", 25) # Access attributes and call methods print(person1.name) print(person1.greet()) print(person1.birthday()) print(person2.greet())
Alice Hello, my name is Alice Happy birthday! Alice is now 31 Hello, my name is Bob
The Person class defines a template with attributes (name, age) initialized in __init__ and methods (greet, birthday). When we create person1 and person2, each becomes a separate object with its own attribute values. The birthday() method modifies person1's age without affecting person2, demonstrating object independence.
Class Attributes vs Instance Attributes
Distinguishing between class-level and object-level attributes.
python
class Animal: # Class attribute (shared by all instances) kingdom = "Animalia" def __init__(self, name, species): # Instance attributes (unique to each object) self.name = name self.species = species def display_info(self): return f"{self.name} ({self.species}) belongs to kingdom {Animal.kingdom}" # Create objects dog = Animal("Rex", "Canis familiaris") cat = Animal("Whiskers", "Felis catus") print(dog.display_info()) print(cat.display_info()) print(f"Kingdom of all animals: {Animal.kingdom}") print(f"Dog's name: {dog.name}") print(f"Cat's name: {cat.name}")
Rex (Canis familiaris) belongs to kingdom Animalia Whiskers (Felis catus) belongs to kingdom Animalia Kingdom of all animals: Animalia Dog's name: Rex Cat's name: Whiskers
The 'kingdom' attribute is a class attribute - it exists once and is shared by all Animal instances. The 'name' and 'species' are instance attributes - each object has its own copies with different values. This demonstrates how classes can store both shared data (class attributes) and individual data (instance attributes).
Constructor and Object Initialization
Understanding how __init__ constructs and initializes objects with specific values.
python
class Book: def __init__(self, title, author, pages, published_year): self.title = title self.author = author self.pages = pages self.published_year = published_year self.is_read = False def mark_as_read(self): self.is_read = True return f"You've finished reading '{self.title}'" def book_age(self, current_year): return current_year - self.published_year def summary(self): read_status = "Read" if self.is_read else "Not read" return f"{self.title} by {self.author} ({self.pages} pages) - {read_status}" # Create objects with different values book1 = Book("Python Basics", "John Doe", 350, 2020) book2 = Book("Advanced Python", "Jane Smith", 450, 2022) print(book1.summary()) book1.mark_as_read() print(book1.summary()) print(f"Book age: {book1.book_age(2024)} years")
Python Basics by John Doe (350 pages) - Not read You've finished reading 'Python Basics' Python Basics by John Doe (350 pages) - Read Book age: 4 years
The __init__ constructor initializes each Book object with specific values passed as arguments. Each object maintains independent state - marking book1 as read doesn't affect book2. The constructor ensures all necessary attributes are set up when the object is created, preventing AttributeError later.
Methods Modifying Object State
Demonstrating how methods can access and modify an object's attributes.
python
class BankAccount: def __init__(self, account_holder, initial_balance): self.account_holder = account_holder self.balance = initial_balance self.transaction_history = [] def deposit(self, amount): if amount > 0: self.balance += amount self.transaction_history.append(f"Deposit: +${amount}") return f"Deposited ${amount}. Current balance: ${self.balance}" return "Deposit amount must be positive" def withdraw(self, amount): if amount > 0 and amount <= self.balance: self.balance -= amount self.transaction_history.append(f"Withdrawal: -${amount}") return f"Withdrew ${amount}. Current balance: ${self.balance}" return "Insufficient funds or invalid amount" def get_statement(self): statement = f"Account: {self.account_holder}\nBalance: ${self.balance}\nTransactions:\n" for transaction in self.transaction_history: statement += f" {transaction}\n" return statement # Create and use account object account = BankAccount("John", 1000) print(account.deposit(500)) print(account.withdraw(200)) print(account.get_statement())
Deposited $500. Current balance: $1500 Withdrew $200. Current balance: $1300 Account: John Balance: $1300 Transactions: Deposit: +$500 Withdrawal: -$200
Each method modifies the object's state (balance and transaction_history). The deposit and withdraw methods validate inputs before changing the state, ensuring data integrity. The object maintains its state between method calls, demonstrating how objects persist data throughout their lifetime.
Dunder Methods (__str__ and __repr__)
Using special methods to customize string representation of objects.
python
class Student: def __init__(self, name, roll_number, gpa): self.name = name self.roll_number = roll_number self.gpa = gpa def __str__(self): return f"Student: {self.name} (Roll: {self.roll_number}, GPA: {self.gpa})" def __repr__(self): return f"Student('{self.name}', {self.roll_number}, {self.gpa})" # Create objects student1 = Student("Alice", 101, 3.8) student2 = Student("Bob", 102, 3.5) print(str(student1)) # Calls __str__ print(repr(student1)) # Calls __repr__ print(student2) # Also calls __str__
Student: Alice (Roll: 101, GPA: 3.8) Student('Alice', 101, 3.8) Student: Bob (Roll: 102, GPA: 3.5)
__str__ provides a user-friendly string representation used by print(). __repr__ provides a detailed representation useful for debugging. These dunder methods make objects more informative when displayed, improving code readability and debugging efficiency.
  • Confusing Class Attributes with Instance Attributes: A frequent mistake is treating all attributes the same. If you define a list as a class attribute (e.g., items = []), all instances share the same list. Modifying it in one instance affects all instances. Correct approach: initialize mutable objects in __init__ as instance attributes so each object gets its own copy.
  • Forgetting to Return from Methods: Beginners often write methods that perform actions but don't return values. For example, a method that calculates something but uses print() instead of return. This makes it impossible to use the result in other code. Always return values from methods that compute or fetch data.
  • Not Calling __init__ Implicitly: Developers sometimes try to call __init__ explicitly (object.__init__()) after creating an object. Python calls __init__ automatically when you create an object using ClassName(). Manually calling it again can cause unexpected behavior or duplicate initialization.
  • Using Mutable Objects as Default Arguments: Don't use mutable defaults like def __init__(self, items=[]). The default list is created once and shared across all instances without arguments. Use def __init__(self, items=None): if items is None: self.items = [] instead.
  • Accessing Private Attributes Directly: Directly accessing attributes meant to be private (like object._private_var) breaks encapsulation. Even though Python allows it, doing so makes code fragile. Use properties or getter methods instead to maintain the intended interface and allow future changes to internal implementation.
  • Creating Methods Without self Parameter: Instance methods must have 'self' as the first parameter to access instance data. Forgetting it makes the method unable to work with the object's attributes. Class methods need @classmethod decorator and 'cls' parameter. Static methods with @staticmethod don't need either.
  • Initialize All Attributes in __init__: Define every instance attribute in the constructor, even if you set them to None initially. This makes the class's interface clear and prevents AttributeError when accessing attributes. Readers can immediately see what attributes an object will have.
  • Use Meaningful Class and Attribute Names: Choose descriptive names that clearly indicate what the class represents and what attributes mean. Use BankAccount instead of BA, customer_email instead of e. Good naming serves as documentation and makes code self-explanatory.
  • Keep Related Data and Methods Together: Group attributes and methods that work together in the same class. Don't scatter related functionality across multiple classes. This cohesion makes code easier to understand, test, and modify.
  • Limit Class Responsibilities: Follow the Single Responsibility Principle - each class should have one reason to change. A User class should handle user data, not file I/O or database queries. Create separate classes for those concerns.
  • Write Docstrings for Classes and Methods: Include docstrings explaining what the class does, what attributes it has, and how to use it. Docstrings for methods should explain parameters and return values. This helps other developers and your future self understand the code's purpose.
  • Use Properties for Controlled Attribute Access: Instead of allowing direct attribute modification, use @property and @attribute.setter decorators to control access and validate data. This allows adding logic later without changing calling code.
What is the difference between a class and an object? Provide examples.
A class is a blueprint, template, or abstract definition of something. It defines what attributes and methods objects should have. An object (instance) is a concrete realization of a class with actual values for its attributes. Example: The class 'Dog' defines that dogs have 'name' and 'age' attributes and can 'bark'. A specific dog, 'Buddy' with age 5, is an object of the Dog class. You can create multiple objects from one class, just like manufacturing multiple cars from one blueprint.
Easy
What is the __init__ method and why is it important?
__init__ is the constructor method in Python that initializes a newly created object. It's automatically called when you create an instance using ClassName(arguments). The method sets up initial attribute values and performs any necessary setup. It ensures objects start in a valid state with all required attributes initialized. Without __init__, you'd need to manually set attributes after creating objects, which is error-prone and inconsistent.
Easy
Explain class attributes and instance attributes. How are they different?
Class attributes are defined at the class level and shared by all instances. They're part of the class itself, not individual objects. Instance attributes are defined in __init__ or other methods and are unique to each object. Example: In a Student class, student_count is a class attribute (tracks total students), while name and ID are instance attributes (differ for each student). Modifying a class attribute affects all instances; modifying an instance attribute affects only that object.
Medium
What does 'self' represent in Python class methods?
'self' is a reference to the specific instance (object) on which the method is called. When you call object.method(), Python automatically passes 'object' as the 'self' parameter. Through 'self', methods access and modify that specific object's attributes. 'self' is a convention; you could technically name it differently, but 'self' is the standard in Python. It's required as the first parameter in instance methods.
Easy
How do you create and use objects from a class?
To create an object, you call the class name with required arguments: object = ClassName(arguments). Python automatically calls __init__ with these arguments. To use the object, you access its attributes (object.attribute) or call its methods (object.method()). Example: account = BankAccount('John', 1000) creates an object, and account.deposit(500) calls a method on that object.
Easy
What are dunder methods and give some examples.
Dunder methods (double underscore methods) are special methods in Python that have special meaning. __init__ is the constructor. __str__ defines string representation for print(). __repr__ provides detailed representation for debugging. __len__ defines behavior of len(). __getitem__ enables indexing. __add__ defines + operator behavior. These allow customizing how objects interact with Python's built-in functions and operators, making objects behave more intuitively.
Medium
How do you prevent direct access to sensitive object attributes?
Use naming conventions: single underscore (_attribute) indicates weak privacy by convention. Double underscore (__attribute) triggers Python's name mangling, making direct access harder. However, Python doesn't enforce true privacy. Better approach: use @property decorator to create getter methods with validation. This maintains a clean interface and allows adding logic without changing calling code. Example: @property def age(self): return self._age @age.setter def age(self, value): if 0 < value < 150: self._age = value.
Medium
What's the difference between instance methods, class methods, and static methods?
Instance methods have 'self' as first parameter and operate on instance data. They access and modify object attributes. Class methods have @classmethod decorator and 'cls' as first parameter, operating on class data. Static methods have @staticmethod decorator, no implicit 'self' or 'cls', and behave like regular functions but belong to the class namespace. Use instance methods for object operations, class methods for class-wide operations, and static methods for utility functions related to the class.
Medium
Create a Phone class with brand, model, and price attributes. Add methods to apply discount and display phone details. Create two phone objects and demonstrate all functionality.
class Phone: def __init__(self, brand, model, price): self.brand = brand self.model = model self.price = price def apply_discount(self, discount_percent): discount_amount = self.price * (discount_percent / 100) self.price -= discount_amount return f"Discount of {discount_percent}% applied. New price: ${self.price:.2f}" def display_details(self): return f"{self.brand} {self.model} - ${self.price:.2f}" phone1 = Phone("Samsung", "Galaxy S21", 999) phone2 = Phone("iPhone", "13 Pro", 1099) print(phone1.display_details()) print(phone1.apply_discount(10)) print(phone2.display_details())
Easy
Create a Movie class with title, director, release_year, and rating attributes. Add methods to update rating, display movie info, and calculate how old the movie is.
class Movie: def __init__(self, title, director, release_year, rating): self.title = title self.director = director self.release_year = release_year self.rating = rating def update_rating(self, new_rating): if 0 <= new_rating <= 10: self.rating = new_rating return f"Rating updated to {new_rating}" return "Rating must be between 0 and 10" def movie_age(self, current_year): return current_year - self.release_year def display_info(self): age = self.movie_age(2024) return f"{self.title} ({self.release_year}) by {self.director} - Rating: {self.rating}/10 - Age: {age} years" movie = Movie("Inception", "Christopher Nolan", 2010, 8.8) print(movie.display_info()) print(movie.update_rating(9.0)) print(movie.display_info())
Easy
Create an Employee class with name, employee_id, salary, and department. Add methods to give raise, calculate annual income with bonus, and display employee info. Create multiple employee objects and demonstrate salary calculations.
class Employee: def __init__(self, name, employee_id, salary, department): self.name = name self.employee_id = employee_id self.salary = salary self.department = department def give_raise(self, percentage): raise_amount = self.salary * (percentage / 100) self.salary += raise_amount return f"Raise of {percentage}% applied. New salary: ${self.salary:.2f}" def annual_income_with_bonus(self, bonus_percent): annual_base = self.salary * 12 bonus = annual_base * (bonus_percent / 100) return annual_base + bonus def display_info(self): return f"Name: {self.name}, ID: {self.employee_id}, Salary: ${self.salary:.2f}, Department: {self.department}" emp1 = Employee("Alice", "E001", 5000, "Engineering") emp2 = Employee("Bob", "E002", 4000, "Sales") print(emp1.display_info()) print(emp1.give_raise(10)) print(f"Annual income with 15% bonus: ${emp1.annual_income_with_bonus(15):,.2f}")
Medium

Classes and objects form the foundation of Object-Oriented Programming in Python. A class serves as a blueprint that defines the structure and behavior of objects, while objects are actual instances of those classes with concrete data. Key concepts include: (1) Class Definition - using the 'class' keyword to create templates, (2) Constructors - __init__ method initializes objects with specific values, (3) Attributes - data stored in objects (instance) or classes (class-level), (4) Methods - functions that operate on object data using 'self', (5) Object Creation - instantiating objects by calling the class like a function, (6) State Management - objects maintain their own state independently. Understanding classes and objects is crucial because they enable code reusability, improve organization, model real-world entities intuitively, and make complex systems manageable. The Python syntax is clean and intuitive, making it easy to learn these concepts. Mastering classes and objects opens the door to understanding inheritance, polymorphism, and advanced OOP patterns.

© 2026 CompileX. Maintained by Aditya Kumar Sharma.