Python
class variables
programming
Python best practices
duplicate question

correct way to define class variables in Python

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Python, class variables are defined inside the class body but outside any method. They are shared across all instances. Instance variables are defined in __init__ using self.name = value and are unique to each instance. The critical rule is: never mutate a mutable class variable (like a list or dict) through an instance — it modifies the shared object. Use class variables for constants and defaults, and instance variables for per-object state.

Class Variables vs Instance Variables

python
1class Dog:
2    # Class variable — shared by all instances
3    species = "Canis familiaris"
4    count = 0
5
6    def __init__(self, name, age):
7        # Instance variables — unique to each instance
8        self.name = name
9        self.age = age
10        Dog.count += 1  # Modify via the class name, not self
11
12a = Dog("Rex", 5)
13b = Dog("Buddy", 3)
14
15print(a.species)   # "Canis familiaris" (read from class)
16print(b.species)   # "Canis familiaris" (same object)
17print(Dog.count)   # 2 (shared counter)
18print(a.name)      # "Rex" (instance-specific)
19print(b.name)      # "Buddy" (instance-specific)

The Mutable Class Variable Trap

python
1class Team:
2    # BAD: mutable class variable shared by all instances
3    members = []
4
5    def add_member(self, name):
6        self.members.append(name)  # Mutates the shared list!
7
8team_a = Team()
9team_b = Team()
10
11team_a.add_member("Alice")
12print(team_b.members)  # ["Alice"] — team_b sees Alice too!
13
14# Both instances share the same list object
15print(team_a.members is team_b.members)  # True

The fix — initialize mutable data in __init__:

python
1class Team:
2    def __init__(self):
3        # Instance variable — each instance gets its own list
4        self.members = []
5
6    def add_member(self, name):
7        self.members.append(name)
8
9team_a = Team()
10team_b = Team()
11
12team_a.add_member("Alice")
13print(team_b.members)  # [] — isolated

Assignment Creates Instance Variables

When you assign to self.x, Python creates an instance variable that shadows the class variable:

python
1class Config:
2    debug = False  # Class variable
3
4c1 = Config()
5c2 = Config()
6
7c1.debug = True  # Creates an INSTANCE variable on c1
8
9print(c1.debug)        # True (instance variable)
10print(c2.debug)        # False (class variable — unchanged)
11print(Config.debug)    # False (class variable — unchanged)
12
13# Check where 'debug' lives
14print("debug" in c1.__dict__)  # True — instance variable exists
15print("debug" in c2.__dict__)  # False — using class variable

When to Use Class Variables

python
1class APIClient:
2    # Constants — use class variables
3    BASE_URL = "https://api.example.com"
4    TIMEOUT = 30
5    MAX_RETRIES = 3
6
7    # Shared counters — use class variables
8    request_count = 0
9
10    def __init__(self, api_key):
11        # Per-instance state — use instance variables
12        self.api_key = api_key
13        self.session = None
14
15    def make_request(self, endpoint):
16        APIClient.request_count += 1  # Always modify via class name
17        url = f"{self.BASE_URL}/{endpoint}"
18        # ...

Class Variables with Type Hints

python
1from typing import ClassVar
2
3class User:
4    # ClassVar marks it as a class variable (for type checkers)
5    max_login_attempts: ClassVar[int] = 5
6    active_users: ClassVar[list[str]] = []
7
8    # Instance variables (declared in __init__)
9    def __init__(self, name: str, email: str):
10        self.name = name
11        self.email = email
12        self.login_attempts = 0

Using slots with Class Variables

python
1class Point:
2    dimensions = 2  # Class variable — OK with __slots__
3
4    __slots__ = ("x", "y")  # Only these instance variables allowed
5
6    def __init__(self, x, y):
7        self.x = x
8        self.y = y
9
10p = Point(1, 2)
11print(p.dimensions)  # 2 (class variable still accessible)
12# p.z = 3  # AttributeError — not in __slots__

Accessing Class Variables

python
1class Counter:
2    total = 0
3
4    def __init__(self):
5        Counter.total += 1  # Modify via class name
6
7    @classmethod
8    def get_total(cls):
9        return cls.total  # Access via cls in classmethod
10
11    def show(self):
12        # Access via self (reads class variable if no instance variable exists)
13        print(f"Total: {self.total}")
14        # Or explicitly via class
15        print(f"Total: {Counter.total}")
16
17c1 = Counter()
18c2 = Counter()
19Counter.get_total()  # 2

Common Pitfalls

  • Defining mutable default values as class variables: Lists, dicts, and sets defined as class variables are shared across all instances. Appending to self.items mutates the shared object. Always initialize mutable data in __init__ with self.items = [].
  • Using self.class_var += 1 instead of ClassName.class_var += 1: For immutable types like int, self.count += 1 creates a new instance variable that shadows the class variable. The class variable remains unchanged. Use ClassName.count += 1 to modify the shared value.
  • Assuming class variable changes propagate to existing instances: If an instance has already created a shadowing instance variable (via assignment), changing the class variable does not affect that instance. The instance variable takes priority in the MRO lookup.
  • Not using ClassVar type annotation for type checkers: Without ClassVar, type checkers like mypy may treat class variables as instance variables. Use typing.ClassVar[T] to explicitly mark shared state.
  • Confusing class variables with __init__ defaults: def __init__(self, items=[]) is a different problem (mutable default argument) but produces the same shared-state bug. The default list is created once and shared across all calls. Use def __init__(self, items=None): self.items = items or [].

Summary

  • Define class variables in the class body (outside methods) for shared constants and counters
  • Define instance variables in __init__ with self.name = value for per-object state
  • Never use mutable class variables (lists, dicts) for per-instance data — initialize them in __init__
  • Modify class variables via ClassName.var, not self.var, to avoid creating instance shadows
  • Use typing.ClassVar[T] to annotate class variables for type checkers

Course illustration
Course illustration

All Rights Reserved.