PYTHON • OBJECT-ORIENTED PROGRAMMING

Class vs. Instance Attributes — Use class attributes vs instance attributes (conceptual)

Understanding where data lives in Python classes determines how your objects share — or protect — their information.

Historical Context & Motivation

Before object-oriented programming (OOP) became widespread, programmers stored data in loose collections of variables and functions. As software grew more complex in the 1960s and 1970s, developers needed a way to bundle data together with the behavior that operates on it. The concept of a class — a blueprint for creating objects — became the central organizing idea, and with it came a critical design question: should a piece of data belong to every object made from the class, or should it belong to the class blueprint itself?

1967
Simula Introduces Classes
The Simula language, created by Ole-Johan Dahl and Kristen Nygaard, introduced classes and objects. It distinguished between data tied to the class template and data unique to each object instance.
1980
Smalltalk Refines the Model
Smalltalk popularized pure object-oriented design. It formalized the idea that class-level variables are shared among all instances, while instance variables are private to each object.
1991
Python Is Released
Guido van Rossum released Python 0.9.0 with built-in support for classes, class attributes, and instance attributes — making OOP accessible to beginners.
2000s
OOP Becomes Standard Practice
Most modern languages (Java, C#, JavaScript ES6, Swift) adopt the class-vs-instance attribute pattern. Python's clean syntax makes it one of the best languages for learning this distinction.

The question at the heart of this lesson is deceptively simple: when you create a variable inside a class, does it belong to the blueprint itself (so every object shares it), or does it belong to each individual object (so each one can have its own value)? Getting this right is fundamental to writing clean, bug-free Python code.

Core Principles & Definitions

In Python, a class is a template or blueprint that describes what data and behavior its objects will have. When you call the class to create an object, you get an instance — a specific, concrete copy built from that template. Attributes are simply variables that store data, and the key distinction is where those variables are defined.

1

Class Attribute

A variable defined directly inside the class body (but outside any method). It is shared by all instances. Changing it on the class affects every instance that hasn't overridden it.
2

Instance Attribute

A variable defined inside a method (usually __init__) using self.name = value. It is unique to each instance.
3

Attribute Lookup Order

Python first checks the instance's own namespace. If the attribute isn't found there, it looks in the class namespace. This is called the MRO (Method Resolution Order) for attributes.
4

Mutability Matters

If a class attribute is a mutable type (like a list), modifying it through an instance changes it for all instances — a common source of bugs.
✦ KEY TAKEAWAY
Think of a class like a school uniform policy. The class attribute is like the school's official uniform color — everyone shares it by default. An instance attribute is like the name stitched on each student's jacket — every student has their own. If the school changes the official color, everyone's default changes. But each student's name tag stays personal.

Visual Explanation

The top box represents the Dog class with its shared class attributes (species, count). The two bottom boxes represent individual instances (dog1 and dog2), each with their own unique instance attributes (name, age). When an instance accesses species, Python looks up to the class because it doesn't find that attribute in the instance's own namespace.

In the diagram above, notice how species lives in the class namespace and is shared. When you write dog1.species, Python doesn't find species on the instance, so it travels up to the class to retrieve it. Meanwhile, self.name and self.age are created in __init__ and stored directly on each instance. This separation is what allows two Dog objects to share a species but have completely different names.

How Attribute Lookup Works

Understanding attribute lookup is the key to mastering class and instance attributes. Every object in Python carries a hidden dictionary called __dict__ that holds its attributes. When you access obj.attribute, Python follows a specific search path.

The Lookup Chain

  1. Step 1: Python checks the instance's __dict__ for the attribute.
  2. Step 2: If not found, Python checks the class's __dict__.
  3. Step 3: If still not found, Python checks parent classes (inheritance chain).
  4. Step 4: If nowhere is it found, Python raises an AttributeError.

The Shadowing Effect

Here's where things get tricky. If you write dog1.species = "Wolf", Python does not modify the class attribute. Instead, it creates a new instance attribute called species on dog1 that shadows (hides) the class attribute. Now dog1.species returns "Wolf", but dog2.species still returns "Canis lupus" because dog2 still falls through to the class.

⚠️ ⚠️ MUTABLE CLASS ATTRIBUTE TRAP
If a class attribute is a mutable object like a list, calling dog1.tricks.append("roll over") modifies the shared list in place. This means dog2.tricks will also show "roll over"! This is one of the most common bugs for Python beginners. Always define mutable data as instance attributes in __init__.

Side-by-Side Comparison

This side-by-side comparison highlights how class attributes (left, in cyan) are defined at the class level and shared, while instance attributes (right, in pink) are defined in __init__ and unique to each object.
Quick-reference comparison of class attributes vs. instance attributes
FeatureClass AttributeInstance Attribute
Defined where?Directly in class bodyInside __init__ using self
ScopeShared across all instancesUnique to each instance
MemoryOne copy in class __dict__One copy per instance __dict__
Use caseConstants, counters, defaultsObject-specific state
Mutable types safe?Risky — changes affect all instancesSafe — each instance gets its own copy

Worked Example — Building a Student Class

Let's build a Student class that tracks the school name (shared by all students) and each student's individual name and GPA. We'll walk through every line and explain where each attribute lives.

Creating a Student Class with Both Attribute Types
1
Step 1 — Define the class with a class attributeWe define school directly in the class body. Every student shares this value. class Student: school = "Lincoln High" total_students = 0
school and total_students are class attributes, stored in Student.__dict__
2
Step 2 — Define __init__ with instance attributesInside __init__, we use self to attach unique data to each instance. We also increment the class counter. def __init__(self, name, gpa): self.name = name self.gpa = gpa Student.total_students += 1
self.name and self.gpa are instance attributes; total_students is updated via the class name
3
Step 3 — Create instances and inspectWe create two students and check what each one sees. s1 = Student("Alice", 3.9) s2 = Student("Bob", 3.5) Now s1.school returns "Lincoln High" (found in class), s1.name returns "Alice" (found in instance), and Student.total_students returns 2.
s1.school → "Lincoln High" | s1.name → "Alice" | Student.total_students → 2
4
Step 4 — Observe the shadowing effectWhat happens if we assign a school to just one student? s1.school = "Jefferson Academy" This does NOT change the class attribute. Instead, Python creates a new instance attribute on s1 that shadows the class attribute. Now s1.school returns "Jefferson Academy", but s2.school still returns "Lincoln High".
s1.school → "Jefferson Academy" (instance) | s2.school → "Lincoln High" (class) | Student.school → "Lincoln High"
💡 TIP: Modifying Class Attributes
To change a class attribute for all instances, always use the class name: Student.school = "New Name". Using self.school = ... or s1.school = ... will only create a local instance attribute that shadows the class one.

When to Use Class vs. Instance Attributes

Choosing the right type of attribute is a design decision. There are clear situations where each type is the best choice, and mixing them up can lead to confusing bugs or poor code organization.

Decision guide for choosing between class and instance attributes
ScenarioUse Class Attribute?Use Instance Attribute?
A value that should be the same for every object (e.g., pi for a Circle class)✅ Yes — define once, share everywhere❌ No — wastes memory repeating the same value
A counter tracking how many instances have been created✅ Yes — it describes the class, not one object❌ No — no single instance "owns" this count
A user's name, email, or score❌ No — each user is different✅ Yes — unique to each object
A list of items (e.g., a shopping cart)❌ Dangerous — mutable class attributes are shared!✅ Yes — create a new list in __init__
A default configuration value that might be overridden✅ Works as a fallback default✅ Can shadow the default per instance
✦ KEY TAKEAWAY
Think of it like a group text versus a direct message. A class attribute is like a group chat announcement — everyone in the class sees the same message. An instance attribute is like a private DM — only that specific object has it. If you need to send something to one person, don't put it in the group chat!

Connections to Advanced OOP Concepts

Understanding class and instance attributes lays the groundwork for several more advanced Python and OOP topics you'll encounter as you continue learning. The distinction between "belongs to the class" and "belongs to the instance" recurs throughout object-oriented design.

How today's concepts connect to advanced OOP topics
This Lesson's ConceptAdvanced Extension
Class attribute (shared data)Class methods — use @classmethod decorator to create methods that operate on class attributes instead of instance data
Instance attribute (per-object data)Properties — use @property to add validation and computed values when getting or setting instance attributes
Attribute lookup chainInheritance — child classes add another level to the lookup chain; the MRO determines which class's attribute is found first
Shadowing a class attributeDescriptors — Python's descriptor protocol (__get__, __set__) gives you fine-grained control over how attribute access is customized
Mutable class attribute trapSlots — using __slots__ restricts which instance attributes can exist, saving memory and preventing accidental attribute creation

As you progress, you'll see that the class-vs-instance pattern applies to methods too: regular methods receive self and operate on instance data, class methods receive cls and operate on class data, and static methods receive neither. Mastering attributes now makes these advanced ideas much easier to grasp.

Practice Problems

PROBLEM 1 — CONCEPTUAL
In your own words, explain the difference between a class attribute and an instance attribute. Give one example of data that should be a class attribute and one that should be an instance attribute for a Car class.
PROBLEM 2 — BASIC
Given the following code, what does print(p2.school) output? class Person: school = "Westfield" def __init__(self, name): self.name = name p1 = Person("Ana") p2 = Person("Leo") p1.school = "Eastside"
PROBLEM 3 — INTERMEDIATE
Study this code carefully. What does the final print output, and why? class Team: members = [] def __init__(self, name): self.name = name Team.members.append(name) t1 = Team("Alice") t2 = Team("Bob") print(t1.members) print(t2.members)
PROBLEM 4 — APPLIED
You're building a BankAccount class. The interest rate is 2.5% for all accounts, each account has its own owner name and balance, and you want to track the total number of accounts created. Write the class definition, identifying which data should be a class attribute and which should be an instance attribute. Explain your reasoning for each.
PROBLEM 5 — CRITICAL THINKING
A classmate argues: "Class attributes are pointless. You can just put everything in __init__ and it works the same." Write a short response explaining at least two reasons why this is wrong, using specific examples from this lesson to support your argument.

Lesson Summary

In Python, class attributes are variables defined directly in the class body and are shared by all instances, making them ideal for constants, counters, and default values. In contrast, instance attributes are defined inside __init__ using self and are unique to each object, perfect for storing data that differs from one instance to another.

Python's attribute lookup chain checks the instance first, then the class, then parent classes. Assigning to an instance can shadow a class attribute without changing it. Be especially careful with mutable class attributes like lists — modifying them in place affects all instances. When in doubt, make it an instance attribute in __init__.

Varsity Tutors • Python • Class vs. Instance Attributes