Python Static Variable in a Class: A Complete Guide
When working with classes in Python, understanding how data is shared across instances is crucial for writing efficient and maintainable code. So unlike instance variables that belong to individual objects, a static variable belongs to the class itself and is shared among all instances. Because of that, one concept that often confuses beginners is the static variable, also known as a class variable. This article will walk you through everything you need to know about Python static variables, from definition and usage to common pitfalls and best practices That's the part that actually makes a difference..
What Is a Static Variable in Python?
A static variable in Python is a variable that is declared inside a class but outside any instance methods. Still, it is not tied to any specific object; instead, it is shared across all instances of that class. When you modify a static variable through one instance, the change is reflected in all other instances as well.
Consider this simple example:
class Car:
wheels = 4 # This is a static variable
def __init__(self, brand):
self.brand = brand # This is an instance variable
In the code above, wheels is a static variable because it is defined directly within the class body. Every Car object will share the same wheels value unless explicitly overridden.
How to Define a Static Variable
Defining a static variable is straightforward. You simply declare the variable at the class level, outside of any method. Here is the basic syntax:
class ClassName:
static_variable = value
You can use various data types for static variables, including integers, strings, lists, and dictionaries. On the flip side, because static variables are shared, mutable types like lists and dictionaries require extra caution.
class Company:
employees = [] # Static variable using a list
name = "TechCorp" # Static variable using a string
Static Variable vs Instance Variable
Understanding the difference between a static variable and an instance variable is fundamental. An instance variable is defined inside methods, typically within __init__, and is prefixed with self. Worth adding: each instance gets its own copy of the instance variable. A static variable, on the other hand, is shared across all instances It's one of those things that adds up..
| Feature | Static Variable | Instance Variable |
|---|---|---|
| Defined | Inside class, outside methods | Inside methods, usually __init__ |
| Belongs to | The class | Individual instances |
| Shared? | Yes | No |
| Accessed via | ClassName.Because of that, variable or self. variable |
`self. |
Accessing Static Variables
You can access a static variable in two ways: through the class name directly or through an instance of the class Most people skip this — try not to..
class Dog:
species = "Canine"
# Accessing via class name
print(Dog.species) # Output: Canine
# Accessing via instance
dog1 = Dog()
print(dog1.species) # Output: Canine
Both approaches return the same value because the static variable is stored in the class's namespace, not in the instance's namespace.
Modifying Static Variables
Modifying a static variable requires careful attention. If you change it through the class name, the change affects all instances. If you assign a value to the variable using self, you create a new instance variable that shadows the static variable for that particular instance only Small thing, real impact..
class Counter:
count = 0
def __init__(self):
Counter.count += 1
a = Counter()
b = Counter()
c = Counter()
print(Counter.count) # Output: 3
In the example above, Counter.count is incremented each time a new instance is created. This demonstrates how static variables can be used to track shared state.
That said, if you do this inside a method:
class Counter:
count = 0
def increment(self):
self.count += 1 # This creates an instance variable!
The first time increment is called, Python looks for count on the instance, does not find it, then looks on the class and finds 0. Subsequent calls modify the instance variable, not the class variable. That said, it then creates a new instance variable count set to 1. This is a common source of bugs.
You'll probably want to bookmark this section Most people skip this — try not to..
Common Use Cases for Static Variables
Static variables are useful in several scenarios:
- Tracking the number of instances: You can use a static variable to count how many objects of a class have been created.
- Shared configuration: When all instances need to share the same configuration value, a static variable avoids redundancy.
- Constants: Static variables can serve as constants that are common to all instances.
- Caching: Shared caches or lookup tables can be implemented using static variables.
class DatabaseConnection:
connection_pool = [] # Shared pool for all instances
max_connections = 10
def __init__(self):
if len(DatabaseConnection.connection_pool) < DatabaseConnection.max_connections:
DatabaseConnection.connection_pool.append(self)
Common Pitfalls and How to Avoid Them
One of the most frequent mistakes with static variables involves mutable default values. Because static variables are shared, modifying a list or dictionary static variable from one instance affects all instances Not complicated — just consistent..
class BadExample:
shared_list = [] # Dangerous!
def add_item(self, item):
self.shared_list.append(item)
obj1 = BadExample()
obj2 = BadExample()
obj1.add_item("hello")
print(obj2.shared_list) # Output: ['hello'] — unexpected!
To avoid this, either use immutable types for static variables or initialize mutable types inside `__init__` as instance variables.
Another pitfall is shadowing. Always use `ClassName.As mentioned earlier, assigning to `self.Day to day, static_var` creates an instance variable that hides the class variable. static_var` when you intend to modify the shared value.
## Scientific Explanation: How Python Handles Static Variables
Under the hood, Python stores class variables in the class's `__dict__` attribute, while instance variables are stored in each instance's `__dict__`. Practically speaking, when you access `self. variable`, Python first checks the instance's dictionary. If the key is not found, it falls back to the class's dictionary. This lookup order explains why reading a static variable through an instance works, but writing to it through `self` creates a new instance entry.
This changes depending on context. Keep that in mind.
```python
class Example:
static_var = 100
obj = Example()
print(obj.__dict__) # {} — no instance variables yet
print(Example.__dict__['static_var']) # 100
obj.static_var = 200
print(obj.Which means __dict__) # {'static_var': 200}
print(Example. static_var) # 100 — unchanged!
This behavior confirms that assignment through an instance creates a new key in the instance dictionary rather
Beyond the basic mechanics, there are several patterns and safeguards that make static (class) variables useful without inviting subtle bugs. Understanding these patterns helps you decide when a class‑level attribute is the right tool and when another approach might be cleaner.
Worth pausing on this one.
### 1. Guarding Mutable Shared State
When a static variable must be mutable—think of a shared cache, a counter, or a connection pool—you should protect concurrent modifications if your code runs in a multithreaded or multiprocessing environment. A simple lock associated with the class does the job:
```python
import threading
class SafeCache:
_cache = {} # shared dictionary
_lock = threading.Lock() # class‑level lock
@classmethod
def get(cls, key):
with cls._lock:
return cls._cache.get(key)
@classmethod
def set(cls, key, value):
with cls._lock:
cls._cache[key] = value
Using a @classmethod makes the intent explicit: the method operates on the class, not on any particular instance, and the lock guarantees that only one thread can mutate _cache at a time.
2. Immutable Constants with typing.Final
If the static variable truly never changes after class definition, annotate it with Final (available from Python 3.8) and, preferably, give it an immutable value:
from typing import Final
class Config:
MAX_RETRIES: Final[int] = 5
TIMEOUT_SECONDS: Final[float] = 30.0
Static analysis tools (mypy, pyright) will then flag any attempt to reassign Config.MAX_RETRIES, catching bugs at development time But it adds up..
3. Avoiding Unintended Shadowing via Properties
Sometimes you want to expose a class variable through an instance attribute but still prevent accidental overwriting. A read‑only property does that:
class Service:
_version = "1.2.3"
@property
def version(self) -> str:
return self.__class__._version
Now service.version reads the shared value, while service.So naturally, version = "2. 0" raises an AttributeError, making the mistake obvious early.
4. Module‑Level Alternatives
For values that are truly global—like application‑wide settings or a singleton registry—consider defining them at the module level instead of inside a class. Module attributes are naturally shared, and you avoid the class‑lookup overhead altogether:
# settings.py
MAX_WORKERS = 4
FEATURE_FLAGS = {"new_ui": True}
Other modules import settings and read/write these names directly. This pattern is common in frameworks (Django’s settings.This leads to py, Flask’s app. config) and keeps the class namespace focused on behavior rather than data.
5. Using __init_subclass__ for Per‑Class Static Data
When you need each subclass to have its own copy of a static‑like attribute (e.g., a default configuration that can be overridden per subclass), __init_subclass__ offers a clean hook:
class BaseProcessor:
_default_config = {"timeout": 10}
def __init_subclass__(cls, **kwargs):
super().Even so, __init_subclass__(**kwargs)
# Give each subclass its own independent dict
cls. _default_config = dict(cls.
class FastProcessor(BaseProcessor):
_default_config["timeout"] = 2 # affects only FastProcessor
print(BaseProcessor._default_config) # {'timeout': 10}
print(FastProcessor._default_config) # {'timeout': 2}
This technique sidesteps the shared‑mutability pitfall while retaining the convenience of class‑level defaults.
6. Caching with functools.lru_cache or weakref.WeakValueDictionary
If the goal is to cache expensive computations across instances, consider using a decorator that stores results in a function‑level cache rather than a class variable:
from functools import lru_cache
class MathHelper:
@staticmethod
@lru_cache(maxsize=128)
def factorial(n: int) -> int:
return 1 if n < 2 else n * MathHelper.factorial(n - 1)
The cache lives on the function object, is automatically bounded, and avoids manual list/dict management. For cases where you want the cache to expire when instances are no longer referenced, a WeakValueDictionary can hold references without preventing garbage collection Which is the point..
Best‑Practice Checklist
| Situation | Recommended Approach |
|---|---|
| Immutable constant shared by all instances | Class attribute annotated with Final (or module‑level constant) |
| Mutable counter or simple flag |
Here's a thinking process:
- Analyze User Input:
- User wants me to continue an article naturally.
- I must not repeat previous text.
- Must finish with a proper conclusion.
- The provided text ends mid-table: "| Mutable counter or simple flag |"
- Identify the Task:
- Complete the article based on the established pattern/style.
- Fill in the missing table row and any remaining content.
- Ensure a proper conclusion at the end.
- No repetition of the given text.
- Scan the Provided Text:
- It's an article about Python class design patterns.
- Sections: Module-level settings,
__init_subclass__for per-class static data, caching withlru_cache/WeakValueDictionary, and a "Best-Practice Checklist" table. - The table has rows for various situations, and the last visible row is incomplete: "| Mutable counter or simple flag |"
- I need to complete that row and possibly add more rows if the table was meant to have more, but I should work with what's there and add a conclusion.
Let me outline the missing parts:
- Complete the table row for "Mutable counter or simple flag"
- Possibly add remaining table rows if the structure suggests more, but I'll keep it concise and finish the table logically.
- Add a conclusion paragraph that wraps up the article.
Looking at the table structure, it has two columns: "Situation" and "Recommended Approach". But the rows shown:
- Immutable constant shared by all instances -> Class attribute annotated with
Final(or module-level constant)
I should complete that row. I could also add a few more common situations to make the table complete, but I need to be careful not to hallucinate too much or deviate from the style. I'll complete the obvious one, and maybe add one or two more that fit naturally, then conclude Worth knowing..
Actually, looking at the prompt: "Continue the article without friction. Do not repeat previous text. Finish with a proper conclusion." I should continue from where it left off, which is the table row. I'll complete the table, then add a conclusion.
Let me draft the completion:
"| Mutable counter or simple flag | Module-level variable or dedicated class with clear mutator methods |"
Or maybe: "| Mutable counter or simple flag | Instance attribute with a property setter, or a dedicated manager object |"
Given the context of the article discussing class vs module level, and __init_subclass__, I'll keep it consistent. I'll complete the table with a few more relevant rows, then conclude.
Actually, I should check if the table has a fixed number of rows. The markdown shows a table with a header row and data rows. I'll complete the incomplete row and add a couple more that fit the theme, then conclude Took long enough..
And yeah — that's actually more nuanced than it sounds.
Let me think of natural completions:
- "Class attribute with mutable default" -> caution about shared references, use
__init_subclass__or instance init - "Per-subclass configuration" ->
__init_subclass__pattern already covered - "Thread-safe counters" ->
threading.Lockoratomicoperations
But I don't want to overstuff. I'll complete the one row, maybe add a second if it feels natural, then conclude.
Actually, re-reading the user's prompt: "Continue the article without friction. Do not repeat previous text. Finish with a proper conclusion." I need to output the continuation starting right after the given text. The given text ends with:
| Mutable counter or simple flag | ```
Wait, it ends with `| Mutable counter or simple flag |` and then nothing? Actually the code block might have ended. Think about it: let me look: the last line is `| Mutable counter or simple flag |` and then the markdown table might continue or end. Now, the user's text shows:
| Situation | Recommended Approach |
|---|---|
| Immutable constant shared by all instances | Class attribute annotated with Final (or module‑level constant) |
| Mutable counter or simple flag | ``` |
It seems the table row for "Mutable counter or simple flag" is incomplete, missing the approach column. The might be from the previous code block? Now, actually, looking at the structure, the is likely closing a code block from earlier? No, the text shows: |
| Mutable counter or simple flag | ```
Wait, the user's message formatting:
| Mutable counter or simple flag | Actually, it's: `| Mutable counter or simple flag |` then on a new line might be part of the article?
The user's text ends with:
| Mutable counter or simple flag | ```
Hmm, the might be a markdown code block delimiter that was mistakenly included, or it's part of the table. Actually, looking at the overall structure, the "Best‑Practice Checklist" is a markdown table. The row ends with `| Mutable counter or simple flag |` and then there's a on the next line?
| Mutable counter or simple flag | ```
I think the is actually the start of a code block that was supposed to be after the table, but it's tangled. Let me look at the very end: ` followed by a new line with the table header again