Class attributes are the mechanism Python provides for storing data that belongs to the class itself rather than to any individual object created from that class. When you define a variable directly inside the class body, outside any method, you are creating a class attribute that is shared across every instance of that class now and in the future. This is fundamentally different from instance attributes set inside the constructor, which give each object its own independent copy of the data. The distinction between class attributes and instance attributes is one of the first conceptual hurdles in Python OOP, and misunderstanding it leads to subtle bugs where changing a value you thought was private to one object silently affects every other object in your program. Getting it right from the start means understanding not just the syntax but the lookup order Python uses when you access an attribute through dot notation.
Every Python class can have both kinds of attributes, and most well-designed classes use both. Instance attributes hold data that varies from object to object: a customer's name, an order's total, a sensor's last reading. Class attributes hold data that is intrinsic to the category: the maximum length of a name field, the tax rate that applies to all orders, the unit of measurement a sensor reports in. A class attribute is written once, in one place, and every part of your program that needs that value reads it from the same source. If the tax rate changes, you update the class attribute, and every method that calculates totals uses the new value without any other changes. This pattern of centralizing shared data is one of the practical benefits of object-oriented design, and it becomes more valuable as programs grow.
If you have worked through the article on creating your first Python class, you already know how to define instance attributes inside the constructor using dot notation on the special first parameter. If you need a refresher on how Python handles variables and assignment more broadly, the article on Python variables explained covers the fundamentals that underpin both kinds of attributes. Class attributes are even simpler syntactically because they sit at the top level of the class body with no prefix. The simplicity of the syntax can be deceiving, however, because the behavior of class attributes changes depending on whether the value is immutable like an integer or string, or mutable like a list or dictionary. Understanding that distinction prevents the most common class attribute mistakes.
Defining and accessing class attributes
A class attribute is defined by writing an assignment statement directly inside the class body, at the same indentation level as method definitions. The name does not use any prefix because the attribute belongs to the class, not to any particular instance. You can access a class attribute through the class name itself or through any instance of the class, and both access paths read the same underlying value. Accessing through the class name is more explicit and makes it obvious that the attribute is shared. Accessing through an instance is convenient when you already have an object and do not want to reference its class by name.
Here is a class that uses a class attribute to store a default discount rate:
class Customer:
discount_rate = 0.05
def __init__(self, name, total_purchases):
self.name = name
self.total_purchases = total_purchases
def apply_discount(self):
return self.total_purchases * (1 - Customer.discount_rate)The discount rate is defined once in the class body with the value 0.05. Every Customer object shares this same value. The apply_discount method accesses it through the class name, which makes the shared nature of the data explicit. If the business runs a promotion and increases the discount, changing one line updates the behavior of every existing and future Customer object instantly.
The method reads the discount through the class name rather than through the object itself, and the choice is intentional. Accessing through the class name guarantees you are reading the class-level value even if an instance happens to have an attribute with the same name. The explicitness of the class name makes the shared nature of the data impossible to miss when reading the code later.
The attribute lookup order and the shadowing trap
When Python encounters dot notation in your code, it does not simply check the object and move on. It follows a defined lookup order that determines which value it finds first. Python checks the instance's own attribute dictionary first. If the name is not found there, Python checks the class the object was created from. If it is still not found, Python walks up the inheritance chain through parent classes until it either finds the name or raises an error. This lookup order, called the method resolution order for methods and the attribute lookup order for data, is the reason class attributes are accessible through instances at all.
The lookup order creates a subtle trap when you assign to an attribute through an instance. Reading an attribute through an instance checks the instance first, finds nothing, falls back to the class, and returns the class-level value. But assigning to an attribute through an instance does not modify the class attribute. Instead, it creates a brand new instance attribute that shadows the class attribute from that point forward for that specific object. Other objects still see the original class attribute.
Here is a concrete demonstration:
class Counter:
limit = 100
first = Counter()
second = Counter()
print(first.limit)
print(second.limit)Both print calls output 100 because neither object has its own limit attribute, and both fall back to the class. Now watch what happens after an assignment through an instance:
first.limit = 200
print(first.limit)
print(second.limit)
print(Counter.limit)The first object now has its own instance attribute called limit with the value 200, which shadows the class attribute. Reading the limit through the first object returns 200. The second object still returns 100 because it has no instance attribute and falls back to the class. Reading through the class name returns 100 because the class attribute was never modified. The assignment looked like it was modifying a shared value, but it actually created an instance-specific override.
When class attributes are the right choice
Class attributes shine in three common scenarios. The first is constants that describe the class itself. An HTTP client class might have a default timeout value that every request method consults when no timeout is explicitly provided. A validation rule class might have a maximum length that every validation method checks against. These values are not specific to any one object; they are properties of the abstraction the class represents.
The second scenario is shared counters and registries that track information across all instances. A connection class might maintain an active connections counter that increments when objects are created and decrements when they are closed, letting any part of the program check how many connections are open. A task class might maintain a total created counter that serves as an auto-incrementing ID generator. These use cases involve mutable class attributes, which require careful handling because all instances share the same mutable object.
The third scenario is default values that instances can optionally override. A form field class might define a default placeholder text as a class attribute, and the constructor might accept an optional placeholder parameter that falls back to the class attribute when not provided. Individual form fields can customize their placeholder while fields that do not need customization inherit the sensible default without any extra code.
Mutable class attributes and the shared list problem
The most common class attribute mistake involves using a mutable default value like a list or dictionary as a class attribute. Because all instances share the same class attribute object, modifying that mutable object through one instance affects every other instance that reads it. This is not a bug in Python; it is a direct consequence of the attribute lookup order and the fact that mutable objects are shared by reference. But it surprises enough beginners that understanding it early saves hours of debugging.
Consider a class that uses a class attribute list to collect all objects that have been created:
class Student:
all_students = []
def __init__(self, name):
self.name = name
Student.all_students.append(self)This pattern works correctly because the list is always accessed through the class name, making the shared nature explicit. Every new Student appends itself to the same list, and reading the list from anywhere in the program sees the complete collection. The key is that the list is never accessed through an instance in a way that could create a shadowing attribute.
The dangerous pattern accesses the mutable class attribute through an instance. If a method appends to a list by reading it through the instance, the append modifies the shared list correctly because the read resolves to the class attribute. But if the same method later assigns a new list through the instance, that assignment creates a shadowing instance attribute on that one object, breaking the sharing and creating an inconsistency. The rule of thumb is straightforward: if a class attribute is mutable and meant to be shared, always access it through the class name, never through an instance.
The relationship between class attributes and instance attributes is a foundational concept that the next article in this section explores from the opposite direction. Instance attributes give each object its own data; class attributes give all objects shared data. Most real classes use both, and knowing which kind of attribute to reach for in each situation is a skill that develops through practice.
Rune AI
Key Insights
- A class attribute is defined directly inside the class body, outside any method, and is shared across all instances of the class.
- Class attributes are accessed through the class name or through any instance, but assigning through an instance creates a shadowing instance attribute rather than modifying the class attribute.
- Use class attributes for values that are the same for every object, like constants, default settings, and cross-instance counters.
- Mutable class attributes like lists and dictionaries require caution because all instances share the same object, and modifying it through one instance affects all others.
- Python resolves attribute names by checking the instance first, then the class, then parent classes in the inheritance chain.
Frequently Asked Questions
When should I use a class attribute instead of an instance attribute?
Can I override a class attribute for a specific object?
Do class attributes behave the same way in Python as static variables in other languages?
Conclusion
Class attributes are Python's mechanism for data that belongs to the class itself rather than to any individual object. They are ideal for constants, default values, shared counters, and configuration that applies uniformly across all instances. Understanding the distinction between class and instance attributes, and especially understanding how Python's attribute lookup order resolves conflicts between the two, is essential for writing predictable object-oriented code.
More in this topic
Python OOP Design Best Practices
Learn the best practices for designing Python classes, from the SOLID principles adapted for Python to practical guidelines for writing maintainable object-oriented code.
Object-Oriented Programming in Python Explained
Learn what object-oriented programming means in Python, why classes and objects matter, and how OOP helps you organize larger programs into manageable, reusable pieces.
Read and Write Large Files in Python
Learn how to read and write large files in Python without running out of memory, using chunked reading, line-by-line iteration, and buffered writes.