Python - Instance vs Class Variables
• Instance variables are unique to each object and stored in `__dict__`, while class variables are shared across all instances and stored in the class namespace
Key Insights
• Instance variables are unique to each object and stored in __dict__, while class variables are shared across all instances and stored in the class namespace
• Python’s attribute lookup follows the MRO (Method Resolution Order): instance → class → parent classes, which can lead to unexpected behavior when mixing variable types
• Mutable class variables create a common pitfall where modifications affect all instances, making them unsuitable for default collections or objects
Understanding the Fundamental Difference
Instance variables belong to individual objects, while class variables belong to the class itself. This distinction affects memory allocation, scope, and how modifications propagate across your application.
class User:
user_count = 0 # Class variable
def __init__(self, name):
self.name = name # Instance variable
User.user_count += 1
user1 = User("Alice")
user2 = User("Bob")
print(user1.name) # Alice (instance-specific)
print(user2.name) # Bob (instance-specific)
print(User.user_count) # 2 (shared across all instances)
print(user1.user_count) # 2 (accessed through instance)
Each User object maintains its own name in the instance’s __dict__. The user_count exists once in the class namespace, shared by all instances.
Attribute Lookup Chain and Shadowing
Python searches for attributes in a specific order. When you access instance.attribute, Python checks the instance dictionary first, then the class, then parent classes following the MRO.
class Config:
timeout = 30 # Class variable
def __init__(self, name):
self.name = name
config1 = Config("primary")
config2 = Config("secondary")
print(config1.timeout) # 30 (found in class)
print(config2.timeout) # 30 (found in class)
# Shadow the class variable with an instance variable
config1.timeout = 60
print(config1.timeout) # 60 (found in instance)
print(config2.timeout) # 30 (still found in class)
print(Config.timeout) # 30 (class variable unchanged)
# Verify the shadowing
print(config1.__dict__) # {'name': 'primary', 'timeout': 60}
print(config2.__dict__) # {'name': 'secondary'}
This shadowing mechanism is powerful but can introduce bugs. Modifying what appears to be a class variable through an instance actually creates a new instance variable, leaving the class variable intact.
The Mutable Class Variable Trap
Mutable class variables are a frequent source of bugs. When you use lists, dictionaries, or custom objects as class variables, all instances share the same object reference.
class ShoppingCart:
items = [] # DANGEROUS: Mutable class variable
def __init__(self, customer):
self.customer = customer
def add_item(self, item):
self.items.append(item)
cart1 = ShoppingCart("Alice")
cart2 = ShoppingCart("Bob")
cart1.add_item("Book")
cart2.add_item("Laptop")
print(cart1.items) # ['Book', 'Laptop'] - UNEXPECTED!
print(cart2.items) # ['Book', 'Laptop'] - UNEXPECTED!
print(cart1.items is cart2.items) # True - Same object
The correct approach initializes mutable objects as instance variables:
class ShoppingCart:
def __init__(self, customer):
self.customer = customer
self.items = [] # Instance variable
def add_item(self, item):
self.items.append(item)
cart1 = ShoppingCart("Alice")
cart2 = ShoppingCart("Bob")
cart1.add_item("Book")
cart2.add_item("Laptop")
print(cart1.items) # ['Book']
print(cart2.items) # ['Laptop']
Legitimate Use Cases for Class Variables
Class variables excel at storing class-level constants, configuration, and shared state that should genuinely affect all instances.
class Database:
connection_pool = [] # Shared resource
max_connections = 10 # Configuration
query_count = 0 # Shared counter
def __init__(self, name):
self.name = name
@classmethod
def execute_query(cls, query):
cls.query_count += 1
# Execute query logic
return f"Executed: {query}"
@classmethod
def get_stats(cls):
return {
'total_queries': cls.query_count,
'max_connections': cls.max_connections
}
db1 = Database("primary")
db2 = Database("replica")
Database.execute_query("SELECT * FROM users")
Database.execute_query("INSERT INTO logs")
print(Database.get_stats()) # {'total_queries': 2, 'max_connections': 10}
Modifying Class Variables Safely
When you need to modify class variables, use the class name or cls parameter to avoid shadowing. Direct modification through instances creates instance variables instead.
class APIClient:
request_count = 0
rate_limit = 100
def __init__(self, api_key):
self.api_key = api_key
def make_request(self):
# Correct: Modify through class name
APIClient.request_count += 1
if APIClient.request_count > APIClient.rate_limit:
raise Exception("Rate limit exceeded")
@classmethod
def reset_counter(cls):
# Correct: Modify through cls parameter
cls.request_count = 0
def wrong_increment(self):
# WRONG: Creates instance variable
self.request_count += 1
client1 = APIClient("key1")
client2 = APIClient("key2")
client1.make_request()
client2.make_request()
print(APIClient.request_count) # 2 (correct)
client1.wrong_increment()
print(client1.request_count) # 1 (instance variable)
print(APIClient.request_count) # 2 (class variable unchanged)
Memory and Performance Implications
Class variables consume memory once per class, while instance variables consume memory for each object. For large-scale applications with thousands of instances, this difference matters.
import sys
class WithClassVar:
constant_data = "x" * 1000 # Shared
def __init__(self, id):
self.id = id
class WithInstanceVar:
def __init__(self, id):
self.id = id
self.constant_data = "x" * 1000 # Duplicated
# Create 1000 instances
class_var_instances = [WithClassVar(i) for i in range(1000)]
instance_var_instances = [WithInstanceVar(i) for i in range(1000)]
# WithClassVar: ~1KB for class variable + 1000 * (small overhead)
# WithInstanceVar: 1000 * (~1KB + small overhead)
Use class variables for truly shared, immutable data like configuration constants, enumerations, or class-level metadata.
Inspection and Debugging
Python provides tools to inspect where variables are stored and distinguish between class and instance variables.
class Product:
category = "General"
def __init__(self, name, price):
self.name = name
self.price = price
product = Product("Widget", 29.99)
# Instance namespace
print(product.__dict__) # {'name': 'Widget', 'price': 29.99}
# Class namespace
print(Product.__dict__['category']) # General
# Check if attribute is in instance
print('name' in product.__dict__) # True
print('category' in product.__dict__) # False
# Get all instance variables
print(vars(product)) # {'name': 'Widget', 'price': 29.99}
Practical Pattern: Configuration with Override
A common pattern combines class variables for defaults with instance variables for overrides:
class Service:
# Class-level defaults
timeout = 30
retry_count = 3
base_url = "https://api.example.com"
def __init__(self, name, **overrides):
self.name = name
# Apply instance-specific overrides
for key, value in overrides.items():
if hasattr(type(self), key):
setattr(self, key, value)
def get_config(self):
return {
'timeout': self.timeout,
'retry_count': self.retry_count,
'base_url': self.base_url
}
# Use defaults
service1 = Service("primary")
print(service1.get_config())
# {'timeout': 30, 'retry_count': 3, 'base_url': 'https://api.example.com'}
# Override specific values
service2 = Service("secondary", timeout=60, retry_count=5)
print(service2.get_config())
# {'timeout': 60, 'retry_count': 5, 'base_url': 'https://api.example.com'}
# Class defaults remain unchanged
print(Service.timeout) # 30
This pattern provides sensible defaults while allowing instance-specific customization without modifying shared state.