Skip to content

An In-Depth Guide to Class Attributes vs. Instance Attributes in Python

Updated: at 03:01 AM

Understanding the difference between class attributes and instance attributes is a fundamental concept in Python object-oriented programming. While they may seem similar at first glance, class attributes and instance attributes have distinct use cases, behaviors, and implications that developers should recognize.

In this comprehensive guide, we will cover the key differences between class attributes and instance attributes in Python. Topics include:

Table of Contents

Open Table of Contents

Class Attributes Overview

A class attribute is a variable that is shared by all instances of a class. Class attributes are defined directly in the class body, outside of any methods.

class MyClass:
    class_attribute = "I am a class attribute!"

    def __init__(self):
        # Constructor
        pass

In the example above, class_attribute is a class attribute because it is declared at the top level of the class body. All instances created from MyClass will share this same variable value.

We can access and modify class attributes directly through the class name:

# Access class attribute
print(MyClass.class_attribute)

# Modify class attribute
MyClass.class_attribute = "New value!"

Class attributes are shared across instances. So if the value is modified, the change is visible to all existing instances:

a = MyClass()
b = MyClass()

print(a.class_attribute) # "New value!"
print(b.class_attribute) # "New value!"

In summary, the key characteristics of class attributes are:

Instance Attributes Overview

Instance attributes are variables that are defined in the __init__() constructor method of a class. Each instance will have a separate copy of the attribute.

class MyClass:
    def __init__(self):
        # Constructor
        self.instance_attribute = "I am an instance attribute!"

Here, self.instance_attribute is an instance attribute because it is defined inside the __init__() method and prefixed with self.

We access instance attributes through a class instance:

a = MyClass()
print(a.instance_attribute)

Each instance will have its own distinct copy of instance_attribute:

a = MyClass()
b = MyClass()

print(a.instance_attribute)
print(b.instance_attribute)

This prints out two equal values, but a and b each have their own copy of instance_attribute.

The key characteristics of instance attributes are:

When to Use Each Type of Attribute

Based on their behavior, class attributes and instance attributes are suited for different use cases:

Use class attributes when:

Use instance attributes when:

As an example, let’s model a Car class:

class Car:

    wheels = 4 # Class attribute

    def __init__(self, make, model):
        self.make = make # Instance attribute
        self.model = model # Instance attribute

Here, wheels is appropriately a class attribute because all Car instances share that property value. However, make and model are instance attributes since they vary across instances.

Lookup Precedence Rules

Class attributes and instance attributes are located in different namespaces in Python. This means attribute lookups will prioritize instance attributes over class attributes.

For example:

class MyClass:

    class_attr = "I am a class attr"

    def __init__(self):
        self.class_attr = "I am an instance attr"

a = MyClass()
print(a.class_attr)

This prints “I am an instance attr” from the instance namespace, not the class namespace.

If no instance attribute exists, Python will fall back to looking up the class attribute. We can force lookup on the class itself using MyClass.class_attr.

So in summary, the attribute lookup precedence is:

  1. Instance attributes
  2. Class attributes
  3. Lookup on parent classes (for inheritance)

Knowing this resolution order avoids situations where a class attribute is unexpectedly hidden or overridden by an instance attribute of the same name.

Mutable vs. Immutable Class Attributes

It is important to distinguish between mutable and immutable class attributes in Python.

Mutable class attributes can be modified by instances:

class MyClass:
    mutable_list = []

a = MyClass()
a.mutable_list.append(1)

print(MyClass.mutable_list) # [1]

All instances see the change to the mutable class attribute value.

Immutable class attributes cannot be changed by instances:

class MyClass:
    immutable_tuple = (1, 2, 3)

a = MyClass()
a.immutable_tuple += (4,) # Error! Tuples are immutable.

Attempting to modify an immutable class attribute will result in an error.

As a best practice, favor immutable class attributes whenever possible to avoid unintended side effects across instances.

Classmethods and Staticmethods

Python supports two special types of methods that can be related to class attributes:

We can use @classmethod to define alternative constructors for a class:

class Date:

    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def from_str(cls, date_str):
        year, month, day = map(int, date_str.split('-'))
        return cls(year, month, day)

# Use alternative constructor
d = Date.from_str('2020-12-31')

The @staticmethod decorator can create utility methods without needing the self or cls reference:

class MathUtils:

    @staticmethod
    def factorial(n):
        # factorial implementation
        ...

print(MathUtils.factorial(6))

These special methods can improve clarity and encapsulation when working with class attributes.

Inheritance Behaviors

When using class inheritance, it is important to understand how class and instance attributes are resolved across parent and child classes.

For class attributes, the lookup starts on the current child class first before checking the parent class chain upwards:

class Parent:
    class_attr = "Parent value"

class Child(Parent):
    class_attr = "Child value"

print(Child.class_attr) # "Child value"

However, instance attributes follow the normal lookup precedence - starting with the instance itself before looking at any classes:

class Parent:
    class_attr = "Parent value"

    def __init__(self):
        self.instance_attr = "Parent instance"

class Child(Parent):
    class_attr = "Child value"

    def __init__(self):
        super().__init__()
        self.instance_attr = "Child instance"

c = Child()
print(c.instance_attr) # "Child instance"

Even though Parent defines instance_attr, we check the Child instance first and find the attribute there.

Understanding attribute precedence and inheritance helps avoid subtle lookup issues when designing class hierarchies.

Best Practices

Here are some key best practices when using class attributes and instance attributes in Python:

Following PEP 8 style guidelines for class and instance attributes will also improve code clarity.

In summary:

Conclusion

Class attributes and instance attributes are foundational concepts in Python object-oriented programming. While they appear similar initially, understanding their key differences allows you to use each type properly and avoid subtle issues.

The most important distinctions to remember are:

Applying best practices around naming, mutability, inheritance, and accessor design will lead to cleaner class architectures leveraging attributes effectively.

This guide provided a comprehensive overview of class attributes versus instance attributes in Python. The key concepts, use cases, examples, and design tips discussed will help you utilize attributes appropriately in your own Python programs.