Instance vs Class Variables in Python

Module 3 • Session 24 • Scope, Variable Shadowing & Attribute Lookup Resolution

instance-variables class-variables namespaces __dict__ variable-shadowing

In Python Object-Oriented Programming, understanding the difference between **instance variables** and **class variables** is critical for proper memory management, state handling, and bug prevention. Attributes in Python live inside dictionary namespaces corresponding either to the individual instance or to the class blueprint itself.

1. Fundamental Distinction & Scope

- **Instance Variables:** Specific to a given object instance. They are declared inside instance methods (typically inside __init__) using the self keyword. Every object holds its own independent copy.
- **Class Variables:** Shared across all instances of a class. They are defined directly within the body of the class, outside of any methods. All objects share a single reference to class variables unless shadowed.

class Employee: # Class Variable (Shared across all instances) company_name = "TechCorp" employee_counter = 0 def __init__(self, name, salary): # Instance Variables (Unique to each instance) self.name = name self.salary = salary # Updating shared class counter safely using class reference Employee.employee_counter += 1 emp1 = Employee("Alice", 75000) emp2 = Employee("Bob", 82000) print(emp1.company_name) # TechCorp print(emp2.company_name) # TechCorp print(Employee.employee_counter) # 2

2. Attribute Lookup Order & Namespaces (`__dict__`)

When you access obj.attribute, Python follows a strict lookup hierarchy:

  1. Search the instance's own namespace: obj.__dict__
  2. If not found, search the class's namespace: type(obj).__dict__
  3. If still not found, traverse the inheritance tree (MRO - Method Resolution Order)
  4. If nowhere to be found, raise AttributeError
class Car: wheels = 4 c1 = Car() c2 = Car() print("Instance c1 namespace:", c1.__dict__) # {} (Empty!) print("Class Car namespace wheels:", Car.__dict__['wheels']) # 4 # c1 reads 'wheels' from Car because c1.__dict__ has no 'wheels' key print(c1.wheels) # 4

3. Variable Shadowing (The Pitfall)

Variable shadowing occurs when an assignment is made to an attribute on an instance reference (`self.variable = value` or `instance.variable = value`). Instead of updating the class variable, Python dynamically creates a new instance variable with the same name, overriding (shadowing) the class variable for that specific instance.

class Config: theme = "Dark" user_a = Config() user_b = Config() # SHADOWING: Assignment creates an instance variable on user_a user_a.theme = "Light" print("user_a theme:", user_a.theme) # Light (Instance level) print("user_b theme:", user_b.theme) # Dark (Falls back to Class level) print("Class Config theme:", Config.theme) # Dark (Unchanged!) print("user_a namespace:", user_a.__dict__) # {'theme': 'Light'} print("user_b namespace:", user_b.__dict__) # {}

4. Comparison Matrix

Feature Instance Variable Class Variable
Declaration Scope Inside methods via self.var_name Directly inside class body, outside methods
Memory Allocation Allocated per object instance Allocated once when the class is defined
Data Sharing Isolated to specific instance Shared across all current and future instances
Access Method self.var or instance.var ClassName.var (Preferred) or instance.var
Modification Effect Affects only that single instance Modifying via ClassName.var affects all instances
Caution with Mutable Class Variables: Modifying a mutable class variable (like appending to a class-level list) mutates the shared object directly, affecting all instances without triggering shadowing!
class Team: members = [] # Shared mutable list (Class Variable) t1 = Team() t2 = Team() t1.members.append("Alice") # Mutates shared object directly! print(t2.members) # ['Alice'] - Affected t2 unintentionally!

5. Real-World Enterprise Scenario: Rate Limiter & Tracker

This example illustrates using class variables to track application-wide statistics alongside instance variables for individual user metrics.

class RateLimiter: # Class-level global counters and defaults max_requests_per_minute = 60 total_system_requests = 0 def __init__(self, user_id, custom_limit=None): self.user_id = user_id # Override limit if custom limit provided, else use class default self.limit = custom_limit if custom_limit is not None else RateLimiter.max_requests_per_minute self.request_count = 0 def make_request(self): if self.request_count < self.limit: self.request_count += 1 RateLimiter.total_system_requests += 1 print(f"User {self.user_id}: Request approved ({self.request_count}/{self.limit})") return True else: print(f"User {self.user_id}: Rate limit exceeded!") return False @classmethod def update_global_limit(cls, new_limit): cls.max_requests_per_minute = new_limit print(f"Global rate limit set to {new_limit}") # Usage u1 = RateLimiter("User_101") # Uses default 60 u2 = RateLimiter("VIP_202", custom_limit=100) # Uses custom limit u1.make_request() u2.make_request() print("Total System Requests:", RateLimiter.total_system_requests)

🏋️ Try It Yourself: Practice Challenges

Challenge 1: Instance Counter

Create a `Student` class with a class variable `total_students`. Increment this counter inside `__init__` so it tracks how many total students were created.

Challenge 2: Fix the Mutable Bug

Convert a faulty class variable `courses = []` into an instance variable inside `__init__` so students don't accidentally share course lists.

Challenge 3: Dynamic Theme Switcher

Define a `UIElement` class with a class variable `default_color = "blue"`. Demonstrate how changing `UIElement.default_color` updates unshadowed instances while preserving custom instance colors.

⚡ Interactive Sandbox (Instance vs Class Vars)
Console Output:
Click "Run Code" above to execute scripts...

📝 Knowledge Check Quiz

1. Where does Python search FIRST when resolving an attribute lookup like `obj.var`?
2. What happens when you run `obj.class_var = 10` where `class_var` was originally defined on the class?
3. What is the safest way to modify a class variable meant to be shared across all instances?
Advertisement
Horizontal Ad Banner Slot (728x90 / Responsive)