A Complete OOP Guide for Beginners (with a Production Mindset).
1. Introduction – Why Python Classes and Objects Matter
I have seen that most students (around the world) start their Python journey by writing small scripts.
In a script, code is centered around the following things:
• They read data from files
• Write code to loop over data
• Finally, print the results and move on
But in my opinion, this is fine for learning Python language syntax. Based on my practical experience, real-world Python applications don’t scale with scripts alone.
Let’s first understand the problem with script-based Python code to have better clarity and understanding.
The Problem with Script-Based Python Code
Suppose we are managing students’ information as shown below:
names = ["Rahul", "Amit"]
ages = [20, 25]
marks = [70, 90]The above written code is good at the beginning.
Once you start adding more names, updating marks, or passing data between functions, this code will become fragile.
Lists can go out of sync easily and silently. Bugs will start appearing. Overall, it would be difficult to manage the code.
In fact, this is a very common problem in beginner Python programs without classes and objects.
In real software projects, developers don’t think in lists. They think in entities.
For example, a Student is a single unit with:
• data – name, age, marks
The above written approach is called Object-Oriented Programming (OOP).
Developers can model real-world problems naturally and safely with Python classes and objects.
Why This Matters for Real Python Developers
Once you understand Python classes and objects:
• It becomes easier to read and extend your code
That’s why Python classes and objects are considered a core Python skill that every student should learn to become a production developer.
In this guide, I will explain Python classes and objects in a simple way with real-world examples.
2. What Is Object-Oriented Programming (OOP) in Python?
This is a key shift from script-based Python programming to real-world Python development.
In object-oriented programming, each real-world concept is treated as a unit that contains two things:
• data (attributes)
To be more precise, Python is an object-oriented language because almost everything in Python is an object.
For example, numbers, strings, lists, and even functions have methods and properties.
If you want to write clean and scalable code, then you should have a good understanding of Python OOP concepts like classes and objects.
3. What Is a Class? (With Example)
A class in Python is a blueprint that contains two things:
• Data (also called attributes)
Let’s understand each in a simple way.
Data
These are the variable names inside a class that store information about an entity.
Methods
These are functions inside a class, and each function defines what actions can be performed using the data.
class Student:
name = "Amit"
marks = 80
def display(self):
print("Name:", self.name)
print("Marks:", self.marks)Explanation
In the above program, we have created a Student class that acts as a blueprint.
This class contains data and a function (method).
Next, we have defined two attributes: name and marks that hold information.
After that, we have defined a method display() that prints these two attributes.
Here, self represents the current object created from the class.
It allows a method to access data and print the data of that same object.
Please note that a class can have multiple attributes and methods as well.
4. What Is an Object in Python? (Simple Explanation for Beginners)
An object is the real instance created from a class.
Example
class Student:
name = "Amit"
marks = 80
def display(self):
print("Name:", self.name)
print("Marks:", self.marks)
s1 = Student()
s1.display()Explanation
In the above example, Student is a class (blueprint).
The below line of code creates a real object from the Student class:
s1 = Student()
Please note that s1 is an object that contains:
• data (name and marks)
• behavior (display)
In the last line, s1.display() is used to call the display method using the s1 object for that specific object.
If you want, you can create multiple objects from the same class like:
s1 = Student()
s2 = Student()
Please note that here s1 and s2 objects are created from the same Student class, but s1 and s2 are different objects.
Each object can hold its own data and methods differently.
5. What Is __init__() Method in Python?
It is also called a constructor in object-oriented programming.
In simple words, it is used to initialize data when a new object is created from a class.
The __init__() method ensures that every object starts with the required data already set (instead of being empty).
This helps to avoid bugs caused by missing values, and this is how code is written in real-world applications.
Example: __init__() Method in Python Class
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
s1 = Student("Amit", 25)Explanation
In the above program, first the s1 object is created with the below line of code:
s1 = Student("Amit", 25)
Then, the __init__() method is called automatically.
The __init__() method receives values (Amit and 25), and these values are assigned to the object’s data.
In this way, Python ensures that the object is created in a valid and usable state (with required data).
6. Why Use self in Python Classes? (Beginner Explanation)
In Python language, self refers to the current object created from a class.
It is also used to store and access data inside that specific object.
Example:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
s1 = Student("Rocky", 22)Explanation:
In the above program, when we write the following statement:
self.name = name
The name on the right-hand side (RHS) is the value passed to the function as a parameter.
This value exists only inside the function (constructor), and self.name on the left-hand side (LHS) is an attribute of the object.
In simple words, the value received (Rocky), which is passed as a function parameter (name), is assigned to an object attribute (self.name).
Note: At runtime, the attribute name is created automatically inside the object when the following statement is executed:
self.name = name
In Python, you don’t need to declare attributes in advance.
Call-flow Illustration:
When Python runs the following line:
s1 = Student("Rocky", 22)
Internally, Python does the following:
Student.__init__(s1, "Rocky", 22)
Explanation:
Object s1 is automatically passed as self.
In simple words, self contains the s1 object reference.
This is how self connects methods to an object, and it also prevents data from mixing between multiple objects.
Now, you have clarity about self in Python.
7. Default Values in __init__()
In real projects, sometimes we don’t have every piece of information available, or we don’t provide all the values to the __init__() method.
Some values are optional, and Python allows this using default values in __init__().
Example:
class User:
def __init__(self, name, role="guest"):
self.name = name
self.role = role
u1 = User("Admin")
u2 = User("Amit", "manager")Explanation:
In the above example, we have set a default value using role="guest".
When we create the u1 object and don’t pass any role, then Python uses the default value "guest" for role.
In the next line, when we create the u2 object and pass role value as "manager", then the default value (guest) is overridden.
Why This Is Useful?
Default values inside the __init__() method give you these benefits:
• Flexibility: Objects can be created even when partial information is available.
• Readability: Objects are created in a clean way, and it becomes easy to understand code.
• Safety: Objects are created with sensible values, so you never miss important data.
8. Multiple Parameters in __init__() (Normal in Real Projects)
In real projects, when you create objects, each object generally represents a complete entity, not just one or two values.
That is why it is very common to use multiple parameters in the __init__() method in production code.
Example: Python Class with Multiple Parameters
class Product:
def __init__(self, id, name, price, stock, category):
self.id = id
self.name = name
self.price = price
self.stock = stock
self.category = categoryExplanation:
In the above code, this class represents a Product with all its important details (like id, name, price, stock, etc.).
Each parameter passed to the __init__() method becomes data stored inside the object.
In real applications, these multiple values can come from multiple sources like databases, APIs, or user input.
When you pass multiple parameters, it ensures that the object is created with complete data.
9. Instance Variables vs Class Variables in Python
In Python classes, you can define two types of variables.
Instance variables belong to each individual object, and class variables belong to the class itself and are shared by all objects.
It is very important to understand the difference between these two variables in Python, because it helps you write correct and bug-free object-oriented Python code.
Let us try to understand each by taking two different examples.
a) Instance Variables (Object-Specific)
These variables are created inside the __init__() method and belong to a specific object.
Example:
class Student:
def __init__(self, name):
self.name = nameExplanation:
In the above shown code, self.name is an instance variable.
When you create an object of the Student class, then each object gets its own copy of name.
If you change one object’s data, then it won’t affect others.
b) Class Variables (Shared by All Objects)
These variables are defined inside the class but outside the __init__() method.
Example:
class Student:
school_name = "XYZ School"
def __init__(self, name):
self.name = nameExplanation:
In the above shown code, school_name is a class variable, and it is shared by all student objects.
Production Example: E-commerce Order System
Let me share a small example here that I have used in one of my real-world projects.
In this project, we used class variables for shared configuration and instance variables for request-specific data, which helped keep the system clean and bug-free.
Example:
class Order:
tax_rate = 0.18
def __init__(self, order_id, amount):
self.order_id = order_id
self.amount = amount
def total_amount(self):
return self.amount + (self.amount * Order.tax_rate)
o1 = Order(201, 1000)
o2 = Order(202, 1500)
print(o1.total_amount())
print(o2.total_amount())Output:
1180.0
1770.0
Explanation:
In the above program, tax_rate is a class variable because it is shared by all orders.
Also, self.order_id and self.amount are instance variables because each order stores its own order id and amount.
The total_amount() method calculates and returns the final amount (including tax) using the shared tax rate.
10. Methods in a Class
A method in a class is a function, and it defines what an object can do.
In simple words, it represents the behavior or actions of an object.
Example: Student Class with Multiple Methods
class Student:
def display(self):
print("Student details")
def is_passed(self):
return True
s1 = Student()
s1.display()
print(s1.is_passed())Output:
Student details
True
In the above program, we have defined two methods in the Student class.
The two method names are:
• display()
After that, an object s1 is created from the Student class.
In the next two lines, both methods are called using the object s1, and outputs are printed.
Please note that a class can have multiple methods (based on need), and each method inside a class represents a different behavior.
11. The __str__() Method (Readable Objects)
The __str__() method defines how an object is converted to text.
Always remember that the __str__() method is called automatically by Python when an object is printed or needs a readable string.
You don’t call it directly.
Example: __str__() in a Student class
class Student:
def __init__(self, name):
self.name = name
def __str__(self):
return f"Student Name: {self.name}"
print(Student("Raj"))Output:
Student Name: Raj
In the above program, firstly we create a class named Student, and inside the Student class, we define two methods:
• __init__()
After that, when we create a new object using Student("Raj"), the constructor __init__() is called automatically, and it initializes the variable name with the value "Raj".
After that, when Python tries to print the object using
The __str__() method returns a readable string.
f"Student Name: {self.name}" is an f-string which allows the value of self.name to be directly inserted into the string, and finally, Python prints a readable output like:
Student Name: Raj
(instead of a memory address).
In Python language, when you write:
print(obj)
Python doesn’t directly print the object.
text = obj.__str__()
12. Encapsulation (Controlled Access)
Encapsulation means hiding internal data and allowing access to it only through defined methods.
It helps in two ways:
• It protects data from being changed directly.
Example:
class Student:
def __init__(self, name, marks):
self.name = name # public variable
self.__marks = marks # private variable
def get_marks(self):
return self.__marks
s = Student("Andy", 75)
print(s.name)
print(s.__marks)Output:
Andy
AttributeError
In the above example, both name and __marks are instance variables.
__marks is a private variable because it represents sensitive data and is defined with double underscore (__).
As a result, you cannot access the __marks variable directly outside the class, but you can access the public variable name without any restriction.
In the above example, encapsulation is achieved by hiding sensitive data (__marks) and exposing it only through class methods.
By doing this, data safety is ensured and you follow the best practices in Python object-oriented programming.
• Private – A private variable or method is accessed only inside the class and is protected from direct access from outside.
13. Production Example: Logger (Extensible Design)
In one of my production projects, we implemented a logger class, and it tracks user actions in a clean and extensible way.
The following code records the user actions along with the exact time they occur. It prints log messages in a structured way like when, who, and what happened.
Example:
from datetime import datetime
class UserLogger:
def __init__(self, username):
self.username = username
def log(self, action):
time = datetime.now().isoformat()
print(f"{time} | {self.username} | {action}")Explanation:
In the above code, firstly we have imported the datetime class from the datetime module so that the current date and time can be captured.
After that, we have defined a class UserLogger that handles logging user actions.
Next, we have initialized the logger with a specific user inside the constructor method.
After that, we have defined a method log() to log a user action. Inside the log() method, we are capturing the current time in a standard and readable format.
Finally, the log message is printed showing when, who, and what.
Important Points:
Normally, the log() method is called in the following scenarios:
• After a user logs in or logs out
• When a file is uploaded or downloaded
• When an error or warning occurs
• When an API request is received
And a production developer places the logging call inside business logic so that every important event gets recorded.
14. Object Lifecycle: Creation → Use → Cleanup
An object lifecycle describes three important things:
• Creation – At this stage, an object is created in memory when you instantiate a class
In real-world applications, I have seen that sometimes bugs appear in an application because objects are not cleaned up properly (not by bad logic).
Let me write a simple code for better understanding.
Example:
class FileTracker:
def __init__(self, filename):
self.filename = filename
print("Opened:", filename)
def process(self):
print("Processing file")
def __del__(self):
print("Closed:", self.filename)
# object creation
tracker = FileTracker("data.txt")
# object usage
tracker.process()
# object deletion
del trackerOutput:
Opened: data.txt
Processing file
Closed: data.txt
Explanation:
In the above program, when FileTracker object is created, it runs the __init__() method and prints "Opened: data.txt".
After that, the process() method is called, then Python executes the body inside process() method and prints "Processing file".
Please note that the process() method represents using the object while it is active.
Finally, when del tracker is executed, Python removes the reference to the object, and if no reference remains, then Python invokes the __del__() method for cleanup tasks (during garbage collection) and prints "Closed: data.txt".
Normally, a __del__() method is used for the following cleanup tasks:
• Closing a file
• Releasing a network connection
• If you want to free an external resource (not Python memory)
• If you want to print a message (for learning perspective)
Golden Lines:
• del only removes references of an object
• __del__() method is used for cleanup tasks
• Python garbage collector releases the object from memory
15. Deleting Objects in Python
In Python, if you want to remove the reference of an object (when it is no longer required), then you can use the del keyword.
Example:
del objImportant Points:
• It only removes the reference, not the object
• There is no immediate guarantee about destruction of the object
• The object is cleaned up only when Python garbage collector decides it is safe
16. Five Common Mistakes Beginners Make in Python OOP
1. Forgetting self
Most of beginners forget to use self inside methods. Without self keyword, Python does not know which object’s data the method should use, and this mistake leads to errors in application.
2. Confusing class with object
Many beginners (from non-CS background) think that class and object are same thing. But class is only a blueprint and object is the instance from that particular class.
3. Putting heavy logic inside __init__():
Many times, freshers write heavy logic inside the __init__() method, but in real-world Python code, the __init__() method is used only to initialize data.
Sometimes, every Java developer ignores the __str__() method, and in that case, unreadable memory addresses are printed.
But one should define the __str__() method because it helps to make objects readable and also improves debugging in Python applications.
17. Why This Matters for AI, ML, and Cloud Development:
1. PyTorch models are classes:
2. ML pipelines manage state using objects:
• model parameters
In Python, we use objects to keep this data together with the logic that operates on it.
3. Cloud services use class-based SDKs:
If you don’t understand Python OOP concepts, then it becomes difficult to understand or do cloud automation.
My advice would be: if you want to work in AI, ML, or Cloud engineering, then you should have an understanding of the following topics:
• Python classes
18. Frequently Asked Questions (Python Classes and Objects)
Ans:
A class is a blueprint that defines properties and methods, while an object is the real instance created from that class.
Ans:
self represents the current object, and it allows Python to store and access instance variables correctly.
Ans:
It is a constructor that is used to initialize object data when a class instance is created.
Ans:
__str__() provides a human-readable representation of an object, mainly for printing and logging.
Ans:
Python classes and objects are used to model real-world entities such as:
They also help to organize data, manage state, and build scalable, maintainable systems.
19. Summary:
If you want to write clean, scalable, and production-ready code, then Python classes and objects are the foundation of object-oriented programming.
In this guide, you have learned the following topics so far:
• how Python classes work internally