In the previous lesson, you learned how to create Python classes and objects, define attributes and methods, and use the self parameter to work with the current object. You created object attributes manually after creating an object.
For example:
class Student:
pass
student1 = Student()
student1.name = "Rahul"
student1.course = "Python"
student1.marks = 92
This approach works, but it becomes inconvenient when we need to create many objects. Imagine creating hundreds of student objects and manually assigning every attribute after each object is created. Python provides a cleaner and more practical solution through the constructor.
In this lesson, you will learn how the __init__() method works, why it is used for object initialization, how self works inside a constructor, and how constructor parameters are converted into instance variables.
A constructor is a special method that is automatically executed when an object is created.
In Python, the commonly used constructor method is:
__init__()
The double underscores on both sides of init are important. The method name must be written exactly as:
__init__
It should not be written as:
init()
or:
_init_()
The constructor is mainly used to initialize the data associated with an object when that object is created.
For example, if we are creating a Student object, we may want the object to immediately contain information such as its name, course, and marks.
A simple constructor can be defined like this:
class Student:
def __init__(self):
print("Student object created")
Now create an object:
student1 = Student()
Output:
Student object created
Notice that we did not explicitly call __init__().
We only wrote:
student1 = Student()
When the object is created, Python automatically executes the __init__() method.
This automatic execution is what makes the constructor useful for initializing objects.
Without a constructor, we can create an object first and then manually assign attributes:
class Student:
pass
student1 = Student()
student1.name = "Rahul"
student1.course = "Python"
student1.marks = 92
This works, but imagine doing the same thing for many students:
student1 = Student()
student1.name = "Rahul"
student1.course = "Python"
student1.marks = 92
student2 = Student()
student2.name = "Priya"
student2.course = "Data Analytics"
student2.marks = 88
student3 = Student()
student3.name = "Amit"
student3.course = "Python"
student3.marks = 79
There is a lot of repeated code.
With a constructor, we can initialize the attributes during object creation:
class Student:
def __init__(self, name, course, marks):
self.name = name
self.course = course
self.marks = marks
Now objects can be created much more conveniently:
student1 = Student("Rahul", "Python", 92)
student2 = Student("Priya", "Data Analytics", 88)
student3 = Student("Amit", "Python", 79)
Each object is initialized with its own information at the time it is created.
Consider this constructor carefully:
class Student:
def __init__(self, name, course, marks):
self.name = name
self.course = course
self.marks = marks
There are four names involved:
selfnamecoursemarksself refers to the current object.
name is the value supplied when the object is created.
course is another value supplied during object creation.
marks is another value supplied during object creation.
The constructor then stores those values as attributes:
self.name = name
self.course = course
self.marks = marks
For example:
student1 = Student("Rahul", "Python", 92)
The value "Rahul" is assigned to name, and then:
self.name = name
stores that value inside the current object.
The same process happens for course and marks.
The first parameter of an instance method is conventionally called self. The same principle applies to the constructor.
def __init__(self, name, course, marks):
self.name = name
self.course = course
self.marks = marks
Here, self represents the object that is currently being initialized.
Suppose we create:
student1 = Student("Rahul", "Python", 92)
In this case, self refers to student1.
If we create another object:
student2 = Student("Priya", "Data Analytics", 88)
then self refers to student2 during that initialization.
This is why different objects can have different attribute values even though they are created from the same class.
This line is one of the most important things for beginners to understand:
self.name = name
At first, it may appear that self.name and name are the same thing. They are not.
The right side:
name
is the parameter received by the constructor.
The left side:
self.name
is the instance variable or attribute belonging to the current object.
For example:
class Student:
def __init__(self, name):
self.name = name
Now:
student1 = Student("Rahul")
The value "Rahul" is passed into the parameter name.
The constructor then stores it in:
student1.name
So after initialization:
student1.name
contains:
"Rahul"
This distinction is essential when working with constructors.
One of the biggest advantages of constructors is that we can create multiple objects from the same class while giving each object different values.
class Student:
def __init__(self, name, course, marks):
self.name = name
self.course = course
self.marks = marks
student1 = Student("Rahul", "Python", 92)
student2 = Student("Priya", "Data Analytics", 88)
student3 = Student("Amit", "Python", 79)
Now each object has its own data.
print(student1.name)
print(student1.course)
print(student1.marks)
print(student2.name)
print(student2.course)
print(student2.marks)
Output:
Rahul
Python
92
Priya
Data Analytics
88
The class is the same, but the constructor initializes each object independently.
Consider this example:
class Student:
def __init__(self, name):
self.name = name
student1 = Student("Rahul")
When Python reaches:
Student("Rahul")
the initialization process can be understood as follows:
Student object.Student object is created.__init__() method."Rahul" is supplied to the name parameter.self refers to the newly created object.self.name = name creates and initializes the object’s name attribute.student1.After this process:
student1.name
contains:
"Rahul"
This automatic initialization is the main reason constructors are so useful.
A class can contain both a constructor and regular methods.
class Student:
def __init__(self, name, course, marks):
self.name = name
self.course = course
self.marks = marks
def display(self):
print("Name:", self.name)
print("Course:", self.course)
print("Marks:", self.marks)
Create an object:
student1 = Student("Rahul", "Data Analytics", 92)
Then call the regular method:
student1.display()
Output:
Name: Rahul
Course: Data Analytics
Marks: 92
The constructor initializes the object, while the display() method performs an operation using the initialized data.
| Feature | Constructor | Regular Method |
|---|---|---|
| Name | __init__() |
Any valid method name |
| Called Automatically | Yes, during object initialization | No |
| Main Purpose | Initialize object state | Perform an operation |
| Typical Use | Create initial attributes | Read, modify, or process object data |
For example:
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def display(self):
print("Product:", self.name)
print("Price:", self.price)
When we create:
product1 = Product("Laptop", 55000)
the constructor automatically initializes name and price.
When we later call:
product1.display()
the regular method performs an action using those values.
Let’s build a practical product class using a constructor.
class Product:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
def display(self):
print("Product:", self.name)
print("Price:", self.price)
print("Stock:", self.stock)
def add_stock(self, quantity):
self.stock += quantity
Create an object:
product1 = Product("Laptop", 55000, 10)
Display the product:
product1.display()
Output:
Product: Laptop
Price: 55000
Stock: 10
Now update the stock:
product1.add_stock(5)
print("Updated Stock:", product1.stock)
Output:
Updated Stock: 15
This example demonstrates how the constructor initializes the initial object state while regular methods can modify that state later.
Without a constructor, we might write:
product1 = Product()
product1.name = "Laptop"
product1.price = 55000
product1.stock = 10
With a constructor, we can write:
product1 = Product("Laptop", 55000, 10)
The second approach is more compact and makes it clear that a Product object requires a name, price, and stock value when it is created.
It also reduces the possibility of accidentally forgetting to initialize one of the required attributes.
There are several mistakes beginners commonly make when learning constructors.
init() instead of __init__().self in the constructor.name with self.name.For example, this is incorrect:
class Student:
def __init__(name, course):
self.name = name
self.course = course
The constructor is missing self.
The correct version is:
class Student:
def __init__(self, name, course):
self.name = name
self.course = course
Another common mistake is:
class Student:
def __init__(self, name):
name = name
This does not create an instance attribute called name. The correct assignment is:
self.name = name
__init__() as the constructor method.self refers to the current object.self.attribute stores those values as instance attributes.The next part will build on this foundation by exploring multiple constructor parameters, default values, optional data, and more practical examples of instance variables.
When a class needs more than one piece of information, the __init__() method can accept multiple parameters. This allows every object to be initialized with its own complete set of data at the moment it is created.
For example, a student may require an ID, name, age, course, and marks. Instead of creating the object and assigning each value separately, we can initialize everything through the constructor.
class Student:
def __init__(self, student_id, name, age, course, marks):
self.student_id = student_id
self.name = name
self.age = age
self.course = course
self.marks = marks
Now we can create a student object by supplying all the required values:
student1 = Student(
101,
"Rahul",
21,
"Data Analytics",
85
)
The constructor automatically stores these values inside the object.
print(student1.student_id)
print(student1.name)
print(student1.age)
print(student1.course)
print(student1.marks)
Output:
101
Rahul
21
Data Analytics
85
This approach becomes particularly useful when an application contains many objects with the same basic structure.
Suppose we want to create three students:
student1 = Student(
101,
"Rahul",
21,
"Data Analytics",
85
)
student2 = Student(
102,
"Priya",
22,
"Python",
91
)
student3 = Student(
103,
"Amit",
20,
"Data Science",
78
)
All three objects come from the same class, but each object contains different information.
We can verify this:
print(student1.name)
print(student2.name)
print(student3.name)
Output:
Rahul
Priya
Amit
The class defines the common structure, while the constructor provides the individual data for each object.
It is important to understand the difference between a constructor parameter and an instance variable.
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks
Here:
name
marks
are constructor parameters.
And:
self.name
self.marks
are instance variables.
The constructor receives the values and stores them inside the object.
student1 = Student("Rahul", 85)
Conceptually:
name → "Rahul"
marks → 85
↓
self.name → "Rahul"
self.marks → 85
The parameters exist while the constructor is executing, whereas the instance variables become part of the object.
Instance variables belong to individual objects. This means that changing one object’s data does not automatically change another object’s data.
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks
student1 = Student("Rahul", 85)
student2 = Student("Priya", 92)
Now:
student1.marks = 90
Only student1‘s marks change.
print(student1.marks)
print(student2.marks)
Output:
90
92
This independent object state is one of the fundamental ideas behind Object-Oriented Programming.
Sometimes every object does not need to receive every value explicitly. Python allows constructor parameters to have default values.
class Student:
def __init__(self, name, course="Python", marks=0):
self.name = name
self.course = course
self.marks = marks
Now we can provide only the name:
student1 = Student("Rahul")
The constructor uses the default values for the remaining parameters.
print(student1.name)
print(student1.course)
print(student1.marks)
Output:
Rahul
Python
0
We can also override the defaults:
student2 = Student(
"Priya",
"Data Analytics",
88
)
Now the object contains:
Name: Rahul
Course: Python
Marks: 0
for the first object, while the second contains:
Name: Priya
Course: Data Analytics
Marks: 88
Default values make constructors more flexible.
Default parameters are particularly useful when some information is optional.
Consider an employee:
class Employee:
def __init__(
self,
name,
department="General",
salary=30000
):
self.name = name
self.department = department
self.salary = salary
We can create an employee using only the required information:
employee1 = Employee("Amit")
The object receives the default department and salary.
We can also provide all values:
employee2 = Employee(
"Priya",
"Analytics",
55000
)
This allows the same class to support different levels of information.
A constructor can contain both required and optional parameters.
class Product:
def __init__(
self,
name,
price,
stock=0
):
self.name = name
self.price = price
self.stock = stock
Here, name and price are required, while stock has a default value.
This works:
product1 = Product("Laptop", 55000)
The stock automatically becomes:
0
We can also specify stock:
product2 = Product(
"Mobile",
25000,
15
)
Now the second product starts with a stock value of 15.
Once attributes have been initialized through __init__(), regular methods can use them.
class Product:
def __init__(self, name, price, stock=0):
self.name = name
self.price = price
self.stock = stock
def calculate_total(self, quantity):
return self.price * quantity
def add_stock(self, quantity):
self.stock += quantity
def display(self):
print("Product:", self.name)
print("Price:", self.price)
print("Stock:", self.stock)
Create an object:
product1 = Product(
"Laptop",
55000,
10
)
Display its information:
product1.display()
Output:
Product: Laptop
Price: 55000
Stock: 10
Calculate the price for three units:
total = product1.calculate_total(3)
print("Total:", total)
Output:
Total: 165000
Now increase the stock:
product1.add_stock(5)
print("Updated Stock:", product1.stock)
Output:
Updated Stock: 15
The constructor initializes the initial state, while regular methods operate on that state.
A constructor can also check whether the values supplied during object creation are acceptable.
For example, suppose a student’s marks should be between 0 and 100:
class Student:
def __init__(self, name, marks):
self.name = name
if 0 <= marks <= 100:
self.marks = marks
else:
self.marks = 0
Now:
student1 = Student("Rahul", 85)
print(student1.marks)
Output:
85
But if an invalid value is supplied:
student2 = Student("Amit", 150)
print(student2.marks)
the constructor prevents the invalid value from becoming the student’s marks.
Validation becomes increasingly important in real applications because objects often receive data from users, databases, APIs, or external systems.
A constructor does not always have to store parameters exactly as they are received. It can also calculate initial values.
For example:
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
self.area = length * width
Create the object:
rectangle1 = Rectangle(10, 5)
The constructor automatically calculates:
self.area
Now we can access it directly:
print(rectangle1.area)
Output:
50
Here, area was not directly supplied when the object was created. Instead, it was calculated from length and width.
The values stored inside an object at a particular moment can be thought of as the object’s state.
For example:
class BankAccount:
def __init__(self, account_holder, balance):
self.account_holder = account_holder
self.balance = balance
Create an account:
account1 = BankAccount(
"Rahul",
50000
)
The initial state of the object includes:
account_holder = "Rahul"
balance = 50000
If a method changes the balance:
class BankAccount:
def __init__(self, account_holder, balance):
self.account_holder = account_holder
self.balance = balance
def deposit(self, amount):
self.balance += amount
then:
account1.deposit(10000)
changes the object’s state from:
balance = 50000
to:
balance = 60000
This idea of an object having a state that can change over time is central to Object-Oriented Programming.
When using default parameters, required parameters should come before parameters with default values.
For example, this is correct:
def __init__(self, name, course="Python"):
self.name = name
self.course = course
But this structure is not valid:
def __init__(self, course="Python", name):
self.course = course
self.name = name
Python requires parameters without default values to come before parameters with default values.
Let’s combine required values, default values, validation, and methods in one practical class.
class Employee:
def __init__(
self,
employee_id,
name,
department="General",
salary=30000
):
self.employee_id = employee_id
self.name = name
self.department = department
if salary >= 0:
self.salary = salary
else:
self.salary = 0
def display(self):
print("Employee ID:", self.employee_id)
print("Name:", self.name)
print("Department:", self.department)
print("Salary:", self.salary)
def annual_salary(self):
return self.salary * 12
Create an employee using only the required information:
employee1 = Employee(
101,
"Rahul"
)
Here, the default values are used for department and salary.
Create another employee with all information:
employee2 = Employee(
102,
"Priya",
"Analytics",
55000
)
Display both:
employee1.display()
print("----------------")
employee2.display()
The same class can therefore create objects with different levels of supplied information while maintaining a consistent structure.
__init__() runs automatically during object initialization.With constructors, Python classes become much cleaner because an object can be created in a valid and useful state from the beginning. In the next part, we will go further into practical constructor design, object validation, calculated attributes, and the relationship between instance variables and class-level data.
In the previous sections, you learned how the __init__() constructor initializes an object and how constructor parameters become instance variables through self. You also learned how to use default values, optional information, validation, and multiple parameters when creating objects.
Now we will take the concept one step further. In real Python programs, constructors do more than simply receive values. They establish the initial state of an object, prepare the object for use, and create the foundation on which other methods operate.
Understanding object state is especially important because an object is not static. Its data can change while the program is running. For example, a bank account balance can increase after a deposit, product stock can decrease after a sale, and a student’s status can change after marks are updated.
The state of an object refers to the values stored inside that object at a particular moment. These values are generally represented by the object’s instance variables. :contentReference[oaicite:0]{index=0}
Consider a simple bank account:
class BankAccount:
def __init__(self, account_holder, balance):
self.account_holder = account_holder
self.balance = balance
Now create an account:
account1 = BankAccount("Rahul", 50000)
At this point, the object’s state contains:
account_holder = "Rahul"
balance = 50000
The constructor has established the initial state of the object.
If the customer deposits ₹10,000, the object’s state changes:
account1.balance += 10000
Now the state becomes:
account_holder = "Rahul"
balance = 60000
The object is still the same account1 object, but its internal data has changed. This changing object state is an important part of Object-Oriented Programming. :contentReference[oaicite:1]{index=1}
Instead of changing attributes directly throughout a program, we can define methods that perform specific operations on the object’s state.
class BankAccount:
def __init__(self, account_holder, balance):
self.account_holder = account_holder
self.balance = balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
self.balance -= amount
Create an account:
account1 = BankAccount("Rahul", 50000)
Now deposit money:
account1.deposit(10000)
print(account1.balance)
Output:
60000
The deposit() method has changed the state of the object. :contentReference[oaicite:2]{index=2}
This approach is useful because the operation related to the account is defined inside the BankAccount class. The class therefore contains both the account’s data and the behavior that operates on that data.
We can also use a method for withdrawal:
account1.withdraw(5000)
print(account1.balance)
Now the balance becomes:
55000
The important idea is:
Object State
↓
Current Attribute Values
↓
Methods
↓
Can Change Object State
In a real application, we should not allow every possible value to modify an object’s state. For example, a bank account should not accept a negative deposit.
We can add validation inside the method:
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
else:
print("Deposit amount must be positive")
Now a valid deposit works:
account1 = BankAccount("Rahul", 50000)
account1.deposit(10000)
print(account1.balance)
Output:
60000
But if we attempt:
account1.deposit(-5000)
the program produces:
Deposit amount must be positive
This demonstrates an important design idea: validation should happen whenever important object data is changed, not only when the object is first created. :contentReference[oaicite:3]{index=3}
Constructor parameters do not have to be limited to strings or numbers. Python objects can store lists, dictionaries, tuples, and other Python objects as instance variables.
For example, consider an employee with a list of skills:
class Employee:
def __init__(self, name, age, salary, skills):
self.name = name
self.age = age
self.salary = salary
self.skills = skills
Now create an employee:
employee1 = Employee(
"Priya",
28,
65000,
["Python", "SQL", "Power BI"]
)
The object contains different types of information:
name is a string.age is an integer.salary is a number.skills is a list.We can access the list normally:
print(employee1.skills)
Output:
['Python', 'SQL', 'Power BI']
We can also access an individual skill:
print(employee1.skills[0])
Output:
Python
This shows that instance variables can contain complex data structures, not just simple values. :contentReference[oaicite:4]{index=4}
Objects can also contain dictionaries.
class Employee:
def __init__(self, name, contact):
self.name = name
self.contact = contact
Now create an object:
employee1 = Employee(
"Rahul",
{
"phone": "9876543210",
"email": "rahul@example.com"
}
)
The dictionary is stored inside the object’s contact attribute.
We can access a particular value:
print(employee1.contact["phone"])
Output:
9876543210
This pattern can be useful when an object contains grouped information. For example, a customer object might contain an address dictionary, or an employee object might contain contact information. :contentReference[oaicite:5]{index=5}
One of the most useful patterns when working with OOP is storing multiple objects inside a Python list.
Consider a simple student class:
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks
We can create several objects directly inside a list:
students = [
Student("Rahul", 92),
Student("Priya", 95),
Student("Amit", 88)
]
Now we can loop through the objects:
for student in students:
print(student.name, student.marks)
Output:
Rahul 92
Priya 95
Amit 88
This pattern is extremely useful in applications that manage collections of records. Instead of keeping separate variables for every student, employee, customer, or product, we can store objects in a collection and process them systematically. :contentReference[oaicite:6]{index=6}
Methods can use instance variables and return calculated results.
For example, a student class can determine a grade from marks:
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks
def get_grade(self):
if self.marks >= 90:
return "A"
elif self.marks >= 75:
return "B"
elif self.marks >= 60:
return "C"
else:
return "D"
Create a student:
student1 = Student("Rahul", 92)
print(student1.get_grade())
Output:
A
Here, the constructor establishes the initial student state, while get_grade() uses that state to calculate a result. :contentReference[oaicite:7]{index=7}
A constructor should primarily prepare an object for use. It can initialize values and perform basic validation, but it should not become a place where every operation in the application is performed.
For example, consider this product class:
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def apply_discount(self, percentage):
self.price -= self.price * percentage / 100
The constructor initializes the product:
product1 = Product("Laptop", 55000)
The discount operation is handled separately:
product1.apply_discount(10)
This separation makes the class easier to understand. The constructor is responsible for preparing the object, while individual methods perform individual operations. :contentReference[oaicite:8]{index=8}
Sometimes information is not available when an object is created. In such cases, None can be used to represent the absence of a value.
class Employee:
def __init__(self, name, manager=None):
self.name = name
self.manager = manager
Now we can create an employee without knowing the manager:
employee1 = Employee("Rahul")
At this point:
employee1.manager
contains:
None
Later, we can assign the manager:
employee1.manager = "Neha"
print(employee1.manager)
Output:
Neha
This is useful when some information may become available later in the life of an object. :contentReference[oaicite:9]{index=9}
Let’s look at object state using product inventory.
class Product:
def __init__(self, name, stock):
self.name = name
self.stock = stock
def sell(self, quantity):
if quantity <= self.stock:
self.stock -= quantity
return True
return False
Create the product:
product1 = Product("Laptop", 10)
print(product1.stock)
Output:
10
Now sell three laptops:
product1.sell(3)
print(product1.stock)
Output:
7
The object has moved from a stock state of 10 to a stock state of 7.
This example clearly demonstrates how constructors establish initial state and methods change that state as the program runs. :contentReference[oaicite:10]{index=10}
A useful design principle is to give a class a clear purpose.
For example:
Student
↓
Student information and behavior
Product
↓
Product information and behavior
BankAccount
↓
Account information and behavior
A class that tries to manage students, products, banking transactions, reports, files, and unrelated operations all at once can become difficult to understand and maintain.
Keeping related data and behavior together makes the class easier to work with and prepares the program for larger OOP concepts later in the course. :contentReference[oaicite:11]{index=11}
| Constructor | Object Method |
|---|---|
| Called automatically during initialization | Called explicitly when needed |
| Usually initializes object state | Usually performs an operation |
Named __init__() |
Can have any valid method name |
| Runs when the object is created | Runs when the method is called |
For example:
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def display(self):
print(self.name, self.price)
When we execute:
product1 = Product("Laptop", 55000)
the constructor runs automatically.
When we execute:
product1.display()
the regular method runs because we explicitly called it.
None can represent information that is not yet available.You now have a much stronger understanding of how constructors create an initial object state and how methods operate on that state. The final part of this lesson will bring these ideas together through revision, practical exercises, interview questions, and a constructor-based mini project.
Now let’s bring together the concepts covered in this lesson: the __init__() constructor, instance variables, methods, object state, and calculated results.
Consider a simple student management system. Each student needs an ID, name, and marks. The class should also provide methods to display the student’s information and calculate the student’s grade.
class Student:
def __init__(self, student_id, name, marks):
self.student_id = student_id
self.name = name
self.marks = marks
def display(self):
print("ID:", self.student_id)
print("Name:", self.name)
print("Marks:", self.marks)
def get_grade(self):
if self.marks >= 90:
return "A"
elif self.marks >= 75:
return "B"
elif self.marks >= 60:
return "C"
else:
return "D"
Now create a student object:
student1 = Student(101, "Rahul", 92)
The constructor automatically initializes the three instance variables:
student_id = 101
name = "Rahul"
marks = 92
We can display the student’s information:
student1.display()
And calculate the grade:
print("Grade:", student1.get_grade())
Output:
ID: 101
Name: Rahul
Marks: 92
Grade: A
This small example demonstrates the complete relationship between the constructor, instance variables, and methods. :contentReference[oaicite:0]{index=0}
When this statement executes:
student1 = Student(101, "Rahul", 92)
Python creates a new Student object and automatically calls the __init__() method.
The values are assigned as follows:
101
↓
self.student_id
"Rahul"
↓
self.name
92
↓
self.marks
The object is now initialized and ready to use.
When we call:
student1.display()
the display() method accesses the object’s instance variables.
When we call:
student1.get_grade()
the method uses self.marks to calculate the appropriate grade.
Therefore, the overall structure is:
Constructor
↓
Initializes Object
↓
Instance Variables
↓
Object State
↓
Methods
↓
Operations / Results
This is one of the fundamental patterns you will repeatedly see when working with Python OOP.
While working with constructors and instance variables, beginners often make a few mistakes.
1. Putting everything inside __init__()
The constructor should primarily initialize the object. Large amounts of unrelated business logic should generally be placed inside separate methods.
2. Changing data without validation
If an attribute represents important information such as a bank balance, product stock, or student marks, the program should consider whether the new value is valid before changing the object’s state.
3. Giving a class too many responsibilities
A class should have a clear purpose. A Student class should primarily deal with student-related information and behavior rather than unrelated application tasks.
4. Forgetting that every object has its own state
If we create:
student1 = Student(101, "Rahul", 92)
student2 = Student(102, "Priya", 95)
then each object maintains its own instance variables.
Changing:
student1.marks = 80
does not automatically change:
student2.marks
5. Using class-level data when data should belong to individual objects
Not every piece of information should be shared. A student’s name and marks belong to that particular student, so they should normally be instance variables.
6. Creating unnecessary objects
Objects should represent meaningful entities in the application. Creating objects without a clear purpose can make a program unnecessarily complicated. :contentReference[oaicite:1]{index=1}
In this lesson, you learned how Python classes and objects work together and how the __init__() constructor initializes an object.
You learned that constructor parameters can be assigned to instance variables using self, allowing every object to maintain its own data.
You also learned how constructors can work with multiple values, default values, optional information, different data types, lists, and dictionaries.
Another important concept was object state. An object’s state is represented by the values stored inside its instance variables at a particular moment. Methods can operate on these values and change the object’s state.
You also learned how multiple objects can be stored inside a list and processed using loops, which is particularly useful when building applications that manage collections of records.
Finally, you learned an important OOP design principle: a class should have a clear responsibility, while the constructor should primarily initialize and prepare the object rather than contain unrelated business logic. :contentReference[oaicite:2]{index=2}
With this foundation, you are now ready for the next major concept in Python OOP: understanding the difference between instance variables and class variables, including why some data belongs to individual objects while other data can be shared across objects of the same class.