Types of Variables
Types of Variables in Python Classes
Our Student class stores a name, roll number, and marks for each student.
class Student:
def __init__(self, name, roll_number, marks):
self.name = name
self.roll_number = roll_number
self.marks = marks
student1 = Student("Rahul", 101, 85)
student2 = Student("Anjali", 102, 92)
Each student has separate details. However, some information—such as the college name—may be common to all students. A method may also need temporary values while performing a calculation.
These different needs lead us to three kinds of variables commonly used in classes:
-
Instance variables hold data belonging to an individual object.
-
Class variables hold data associated with the class.
-
Local variables hold values within a function or method call.
Let us extend the Student program to see how each one behaves.
Storing Data for Each Student
Consider the assignments inside __init__():
self.name = name self.roll_number = roll_number self.marks = marks
The attributes created through self are instance variables. Each student object receives its own attributes.
print(student1.name, student1.marks) print(student2.name, student2.marks)
Output:
Rahul 85 Anjali 92
Updating one object does not change the other:
student1.marks = 90 print(student1.marks) print(student2.marks)
Output:
90 92
Here, student1.marks and student2.marks belong to different objects.
We use instance variables for values that can differ from one object to another, such as an employee’s salary, a product’s price, or a bank account’s balance.
Creating Instance Variables in Another Method
Instance variables do not have to be created inside __init__(). Another instance method can create them too.
class Student:
def __init__(self, name):
self.name = name
def assign_grade(self, grade):
self.grade = grade
student1 = Student("Rahul")
student1.assign_grade("A")
print(student1.name)
print(student1.grade)
Output:
Rahul A
The grade attribute is created when assign_grade() runs. Accessing it before that call would raise an AttributeError.
For attributes that every object should have from the beginning, initializing them inside __init__() makes the class easier to use.
Adding a Common College Name
Suppose all students belong to the same college. We can define the college name directly inside the class, outside its methods.
class Student:
college_name = "Cambridge Institute of Technology"
def __init__(self, name, roll_number, marks):
self.name = name
self.roll_number = roll_number
self.marks = marks
Here, college_name is a class variable.
We can access it through the class name:
print(Student.college_name)
Output:
Cambridge Institute of Technology
Objects can also access this class attribute:
student1 = Student("Rahul", 101, 85)
student2 = Student("Anjali", 102, 92)
print(student1.college_name)
print(student2.college_name)
Output:
Cambridge Institute of Technology Cambridge Institute of Technology
In this example, neither object has its own college_name attribute, so Python finds the value in the class.
Using Student.college_name makes it clear that we are accessing class-level data.
Updating the Class Variable
Suppose the college changes its name.
Student.college_name = "ABC Institute of Technology" print(student1.college_name) print(student2.college_name)
Output:
ABC Institute of Technology ABC Institute of Technology
Both objects access the updated class attribute.
A student created afterward also sees the same value:
student3 = Student("Kiran", 103, 78)
print(student3.college_name)
Output:
ABC Institute of Technology
Assigning through an Object
Now consider this assignment:
student1.college_name = "XYZ College"
For this ordinary class attribute, the assignment creates an instance attribute named college_name on student1. It does not update Student.college_name.
print(student1.college_name) print(student2.college_name) print(Student.college_name)
Output:
XYZ College ABC Institute of Technology ABC Institute of Technology
The instance attribute on student1 now shadows the class attribute: accessing student1.college_name finds the instance’s value first.
| Expression | Value comes from |
|---|---|
| student1.college_name | The instance attribute on student1 |
| student2.college_name | The class attribute |
| Student.college_name | The class attribute |
To update the common college name in this example, assign through the class:
Student.college_name = "New College Name"
An object with its own college_name attribute will continue to use its own value.
Using Temporary Values inside a Method
A method may need a value only while performing a calculation.
Suppose we want to calculate how many additional marks a student needs to pass.
class Student:
college_name = "ABC College"
def __init__(self, name, roll_number, marks):
self.name = name
self.roll_number = roll_number
self.marks = marks
def marks_needed_to_pass(self):
pass_mark = 40
required_marks = max(0, pass_mark - self.marks)
return required_marks
Inside marks_needed_to_pass():
pass_mark = 40 required_marks = max(0, pass_mark - self.marks)
pass_mark and required_marks are local variables. They are local to that method call.
student1 = Student("Rahul", 101, 32)
print("Additional Marks Needed:", student1.marks_needed_to_pass())
Output:
Additional Marks Needed: 8
For a student who has already passed:
student2 = Student("Anjali", 102, 85)
print("Additional Marks Needed:", student2.marks_needed_to_pass())
Output:
Additional Marks Needed: 0
The max() function selects the larger of 0 and the calculated difference, preventing a negative result.
The local variables are not stored as attributes of the student.
print(student1.pass_mark)
This raises an AttributeError.
Returning required_marks makes its value available to the caller; it does not turn the local variable into an instance variable.
Parameters and Instance Variables
Let us revisit one line from __init__():
self.marks = marks
Although the names are similar, they have different roles.
| Name | Role |
|---|---|
| marks | A parameter local to the method call |
| self.marks | An attribute stored on the instance |
The assignment copies the reference held by the local parameter into the object’s attribute.
This difference matters when updating data. Consider:
def update_marks(self, new_marks):
marks = new_marks
This only assigns a local variable named marks. It does not update the object.
The correct method is:
def update_marks(self, new_marks):
self.marks = new_marks
Using self.marks identifies the attribute we want to change.
Combining the Three Types
The following program uses a class variable, instance variables, and a local variable together.
class Student:
college_name = "ABC College"
def __init__(self, name, roll_number, marks):
self.name = name
self.roll_number = roll_number
self.marks = marks
def display_details(self):
result = "Pass" if self.marks >= 40 else "Fail"
print("Name:", self.name)
print("Roll Number:", self.roll_number)
print("College:", Student.college_name)
print("Marks:", self.marks)
print("Result:", result)
def update_marks(self, new_marks):
self.marks = new_marks
student1 = Student("Rahul", 101, 85)
student2 = Student("Anjali", 102, 35)
student1.display_details()
print()
student2.display_details()
Output:
Name: Rahul Roll Number: 101 College: ABC College Marks: 85 Result: Pass Name: Anjali Roll Number: 102 College: ABC College Marks: 35 Result: Fail
In this program:
| Variable | Type | Purpose |
|---|---|---|
| college_name | Class variable | Stores the common college name |
| self.name | Instance variable | Stores one student’s name |
| self.roll_number | Instance variable | Stores one student’s roll number |
| self.marks | Instance variable | Stores one student’s marks |
| result | Local variable | Holds the result during the display method call |
The conditional expression:
result = "Pass" if self.marks >= 40 else "Fail"
is a shorter way to write:
if self.marks >= 40:
result = "Pass"
else:
result = "Fail"
Being Careful with Lists as Class Variables
A class variable can hold a list. Because lists are mutable, changes to that shared list can be visible through every instance that accesses it.
Consider this program:
class Student:
skills = []
def __init__(self, name):
self.name = name
student1 = Student("Rahul")
student2 = Student("Anjali")
student1.skills.append("Python")
print(student1.skills)
print(student2.skills)
Output:
['Python'] ['Python']
Both objects access the same class-level list. Calling append() modifies that list; it does not create a separate instance attribute.
If each student should have an individual skills list, create it inside __init__():
class Student:
def __init__(self, name):
self.name = name
self.skills = []
student1 = Student("Rahul")
student2 = Student("Anjali")
student1.skills.append("Python")
student2.skills.append("Java")
print(student1.skills)
print(student2.skills)
Output:
['Python'] ['Java']
Each execution of self.skills = [] creates a new list for that instance.
Practice
Create an Employee class with the following data:
| Data | Variable type |
|---|---|
| company_name | Class variable |
| name | Instance variable |
| monthly_salary | Instance variable |
Add a method named get_annual_salary() that:
-
Calculates monthly_salary * 12.
-
Stores the result in a local variable named annual_salary.
-
Returns the calculated value.
Create two employees:
employee1 = Employee("Rahul", 30000)
employee2 = Employee("Anjali", 40000)
Display their annual salaries. The expected values are 360000 and 480000.
Then change the company name through the class and verify that both employees access the updated name.
We have now seen how variables belong to objects, classes, and method calls. Next, we will extend the discussion to local, enclosing, global, and built-in scopes, and follow how Python finds a variable when its name is used.