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.

Code braces with terminal and markup symbols representing Python programming.
Inside this lesson 10 sections 5 questions 5 figures

SECTION 1

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.

SECTION 2

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.

SECTION 3

Visual Explanation

Class vs. Instance Attributes — Memory ModelClass: Dogspecies = "Canis lupus"(class attribute — shared by all)count = 0(class attribute — tracks total dogs)creates instances ↓Instance: dog1self.name = "Buddy"self.age = 3(instance attributes — unique)dog1.species → looks up to class→ "Canis lupus"Instance: dog2self.name = "Luna"self.age = 5(instance attributes — unique)dog2.species → looks up to class→ "Canis lupus"Both instances share species, but each has its own name and age
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.

SECTION 4

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__.

SECTION 5

Side-by-Side Comparison

Class Attribute vs. Instance Attribute — Code ComparisonClass AttributeDefinition syntax:class Dog: species = "Canis" count = 0Where it lives:In the class's __dict__Shared or unique?SHAREDBest used for:• Constants (e.g., species)• Counters shared by all• Default valuesAccess via:Dog.species or dog1.speciesInstance AttributeDefinition syntax:def __init__(self, name): self.name = name self.age = 0Where it lives:In the instance's __dict__Shared or unique?UNIQUEBest used for:• Object-specific data (name)• Mutable data (lists, dicts)• State that varies per objectAccess via:dog1.name (only that instance)
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.
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

Quick-reference comparison of class attributes vs. instance attributes

SECTION 6

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. 1

    Step 1 — Define the class with a class attribute

    We 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. 2

    Step 2 — Define __init__ with instance attributes

    Inside __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. 3

    Step 3 — Create instances and inspect

    We 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. 4

    Step 4 — Observe the shadowing effect

    What 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.

SECTION 7

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.

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

Decision guide for choosing between class and instance attributes

✦

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!

SECTION 8

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.

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

How today's concepts connect to advanced OOP topics

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.

SECTION 9

Practice Problems

Question 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.
Show answer
A class attribute is a variable defined directly in the class body that is shared by all instances of that class. An instance attribute is defined inside __init__ (or another method) using self and is unique to each object. For a Car class, wheels = 4 makes sense as a class attribute because (nearly) all cars have four wheels. However, self.color should be an instance attribute because each car can be a different color.

Question 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"
Show answer
The output is "Westfield". When we write p1.school = "Eastside", Python creates a new instance attribute on p1 that shadows the class attribute. This does not change the class attribute itself. Since p2 has no instance attribute called school, Python's lookup chain goes to the class and finds "Westfield". The key rule is: assigning via an instance creates an instance attribute; it never modifies the class attribute.

Question 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)
Show answer
Both print statements output ["Alice", "Bob"]. This is the mutable class attribute trap in action. The list members is a class attribute, meaning there is only one list object shared across all instances. When we call Team.members.append(name), we're modifying that single shared list in place. Both t1 and t2 point to the same list, so both see all the names. If each team should have its own member list, members should be initialized as self.members = [] inside __init__.

Question 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.
Show answer
Here's one correct implementation: class BankAccount: interest_rate = 0.025 total_accounts = 0 def __init__(self, owner, balance=0): self.owner = owner self.balance = balance BankAccount.total_accounts += 1 The interest_rate is a class attribute because the bank sets one rate for all accounts — it would be wasteful and error-prone to store 0.025 on every instance. Similarly, total_accounts is a class attribute because it describes the class as a whole, not any single account. The owner and balance are instance attributes because they are unique to each account — Alice's balance shouldn't change when Bob deposits money. We increment total_accounts using BankAccount.total_accounts += 1 (the class name) to ensure we modify the class attribute, not accidentally create an instance attribute.

Question 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.
Show answer
Your classmate is wrong for several reasons. First, class attributes save memory and ensure consistency. If you have 10,000 Student objects and each stores school = "Lincoln High" as an instance attribute, that's 10,000 copies of the same string. With a class attribute, there's exactly one copy. Second, class attributes can serve as shared state that genuinely needs to be unified — like a counter (total_students) that tracks how many instances exist. If this were an instance attribute, each object would only know about itself and couldn't provide a global count. Third, class attributes make code intent clear: when a developer reads species = "Canis lupus" at the class level, they immediately understand this is a shared constant, not something that varies per dog. Design choices communicate meaning to other programmers, and choosing the right attribute type is part of writing readable, maintainable code.

SUMMARY

Lesson Summary

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