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:

  1. Calculates monthly_salary * 12.

  2. Stores the result in a local variable named annual_salary.

  3. 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.

Previous Topic Initializing Objects and Adding Methods