Python Class Attributes Explained

Learn what class attributes are in Python, how they differ from instance attributes, and when sharing data across all objects of a class is the right design choice.

6 min read

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:

pythonpython
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:

pythonpython
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:

pythonpython
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:

pythonpython
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

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.
RunePowered by Rune AI

Frequently Asked Questions

When should I use a class attribute instead of an instance attribute?

Use a class attribute when a value should be the same for every object of that class, either because it is a constant that describes the class itself (like a species name for an Animal class) or because it tracks something that spans all instances (like a counter of how many objects have been created). Use an instance attribute when each object needs its own independent value, like a name or a balance.

Can I override a class attribute for a specific object?

Yes, but the behavior is subtle. Assigning to `object.class_attribute` creates a new instance attribute on that specific object that shadows the class attribute. The class attribute itself remains unchanged, and every other object still sees the original value. Reading `object.class_attribute` checks the instance first, and if no instance attribute exists with that name, falls back to the class attribute.

Do class attributes behave the same way in Python as static variables in other languages?

They serve a similar purpose but are implemented differently. In languages like Java or C++, static variables belong to the class and are accessed through the class name. Python class attributes also belong to the class, but they can be accessed through instances as well due to Python's attribute lookup order. This dual access path is convenient but can cause confusion when a class attribute holds a mutable object like a list.

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.