Python Class Attributes vs Instance Attributes
1Python Class Attributes: overview
A class attribute is a variable stored once on the class and shared by every object, while an instance attribute is stored on a single object and belongs only to it; when you read an attribute, Python checks the object first and the class second.
Before this lesson: Python Classes and Objects
2Python Class Attributes: diagram
3Python Class Attributes: explained step by step
In the last lesson you met one class attribute in passing. This lesson looks at them properly, because the difference between data that belongs to the class and data that belongs to one object is behind some of the most confusing bugs in beginner object-oriented code.
Two places to store data
An attribute can be created in two places:
- In the class body, directly under
classand outside every method. This makes a class attribute: one value, stored once on the class and shared by every object. - In
__init__, onself. This makes an instance attribute: every object gets its own copy.
class Player:
game = "Space Race" # class attribute: one copy, shared
def __init__(self, name):
self.name = name # instance attribute: one copy per object
asha = Player("Asha")
ben = Player("Ben")
print(asha.name, ben.name)
print(asha.game, ben.game, Player.game)Both players can read game, yet neither of them stores it. The value lives on Player, and every object finds it there.
Class constants
The most common use of a class attribute is a value that is the same for every object and rarely changes: a tax rate, a speed limit, a default size. By convention such names are written in UPPER_CASE. Keeping the value in one place means you change it once, instead of hunting through every method that used a copy of the number.
class Scooter:
MAX_SPEED = 25
def __init__(self, name):
self.name = name
self.speed = 0
def accelerate(self, amount):
self.speed = min(self.speed + amount, self.MAX_SPEED)
kick = Scooter("Kick")
kick.accelerate(20)
kick.accelerate(20)
print(kick.speed, Scooter.MAX_SPEED)Inside a method you can read the constant as self.MAX_SPEED or as Scooter.MAX_SPEED. Outside the class, both kick.MAX_SPEED and Scooter.MAX_SPEED work.
A counter shared by all objects
A class attribute is also the natural home for facts about the class as a whole, such as "how many objects have been created". Each time __init__ runs, it adds one to the value stored on the class:
class Robot:
built = 0 # shared: how many robots exist
def __init__(self, name):
self.name = name
Robot.built += 1 # read Robot.built, add 1, store it back on the class
Robot("R2")
Robot("R3")
Robot("Eve")
print(Robot.built)Notice that the line uses the class name, not self. The next two sections explain why that matters.
Where Python looks: instance first, then class
When you write obj.speed, Python has to decide where speed lives. It always searches in the same order:
- the object's own attributes (what
__init__stored onself), - then the class's attributes,
- and if neither has the name, it raises
AttributeError.
That one rule explains everything else on this page.
The shadowing surprise
Reading an attribute can fall back to the class. Assigning never does: obj.unit = "F" always stores the value on the object, even if the class already has a unit. The new instance attribute then shadows (hides) the class one, for that object only.
class Thermostat:
unit = "C"
hall = Thermostat()
attic = Thermostat()
attic.unit = "F" # creates a NEW attribute on attic only
print(hall.unit, attic.unit, Thermostat.unit)
Thermostat.unit = "K" # changes the class attribute itself
print(hall.unit, attic.unit, Thermostat.unit)After attic.unit = "F", the attic has its own unit and the class still says "C". Changing the class later reaches every object that has not shadowed the name (hall), but not the one that has (attic).
This is also why self.built += 1 would be a bug in the counter above. It means self.built = self.built + 1: Python reads the class value (0), adds one, and stores 1 on the object as a brand-new instance attribute. The shared counter never moves. To change the class attribute, assign through the class: Robot.built += 1.
The shared-list pitfall
Shadowing only happens when you assign. Methods like append() change an existing list in place, so no assignment happens, nothing is shadowed, and every object sees the change. That makes a mutable class attribute (a list, dict or set) a trap when each object is meant to have its own:
class SharedPlaylist:
songs = [] # ONE list for every playlist
def add(self, title):
self.songs.append(title)
class OwnPlaylist:
def __init__(self):
self.songs = [] # a fresh list for each playlist
def add(self, title):
self.songs.append(title)
gym, road = SharedPlaylist(), SharedPlaylist()
gym.add("Eye of the Tiger")
print(road.songs)
chill, focus = OwnPlaylist(), OwnPlaylist()
chill.add("Weightless")
print(focus.songs)The rule of thumb: anything mutable that belongs to one object must be created inside __init__.
Look inside with vars() and __dict__
When you are unsure where an attribute lives, ask Python. vars(obj) (the same dictionary as obj.__dict__) holds only the object's own attributes. The class keeps its attributes in Class.__dict__, which also contains some built-in entries starting with __, so filter those out and sorted() the names to get a tidy, predictable list.
class Dog:
species = "Canis familiaris"
def __init__(self, name):
self.name = name
def bark(self):
return f"{self.name} says woof"
rex = Dog("Rex")
print(vars(rex))
print(sorted(key for key in Dog.__dict__ if not key.startswith("__")))
print("species" in vars(rex), rex.species)The last line is a handy test for shadowing: if a name is in vars(obj), the object owns it; if not, Python is finding it on the class.
Which one should I use?
- Different for every object (a name, a score, a balance)? Instance attribute, set in
__init__. - The same for every object (a rate, a limit, a default)? Class attribute, written in
UPPER_CASE. - A fact about the class as a whole (how many objects exist)? Class attribute, changed through the class name.
- A list, dict or set that each object should own? Create it in
__init__, never in the class body.
4Python Class Attributes: syntax
class Name:
CONSTANT = 0.2 # class attribute: shared, written in the class body
count = 0 # class-level counter
def __init__(self, x):
self.x = x # instance attribute: one per object
Name.count += 1 # change the class attribute (not self.count!)
obj = Name(1)
obj.x # the object's own attribute
obj.CONSTANT # not on obj, so Python finds it on the class
Name.CONSTANT # read it through the class
obj.CONSTANT = 0.5 # creates a NEW instance attribute (shadows the class one)
Name.count = 10 # changes it for every object that has not shadowed it
vars(obj) # the object's own attributes only
sorted(Name.__dict__) # the names stored on the class
5Python Class Attributes: run the example
Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.
class Product:
TAX_RATE = 0.20
created = 0
def __init__(self, name, price):
self.name = name
self.price = price
Product.created += 1
def price_with_tax(self):
return round(self.price * (1 + self.TAX_RATE), 2)
pen = Product("Pen", 2.50)
lamp = Product("Lamp", 40)
print(pen.name, pen.price_with_tax())
print(lamp.name, lamp.price_with_tax())
print("Products created:", Product.created)
print("Tax rate:", pen.TAX_RATE, Product.TAX_RATE)Expected outputPen 3.0
Lamp 48.0
Products created: 2
Tax rate: 0.2 0.2
7Python Class Attributes: how the example works
Hover or tap a step to highlight its lines in the example above.
- Line 1
class Product:starts the blueprint. - Line 2-3These two names sit in the class body, outside any method, so they are class attributes.
TAX_RATEis a constant shared by every product, andcreatedis a counter for the class as a whole. Each exists exactly once, onProduct. - Line 5-7
__init__storesnameandpriceonself. These are instance attributes, so every product gets its own pair of values. - Line 8
Product.created += 1uses the class name on purpose, so the shared counter on the class changes. Writingself.created += 1here would create a separate counter on each object instead. - Line 10-11
self.TAX_RATEis not stored on the object, so Python falls back to the class and finds0.20there. Prices are rounded to two decimals. - Line 13-14Each call runs
__init__once, so the counter goes from 0 to 1 and then to 2. - Line 15-16Both products use the same tax rate but their own price: 2.50 * 1.2 is 3.0 and 40 * 1.2 is 48.0.
- Line 17The counter now reads 2. Reading it through the class name always gives the shared value.
- Line 18
pen.TAX_RATEandProduct.TAX_RATEreach the same value: the object has no copy of its own, so Python looks on the class.
8Python Class Attributes: common mistakes
Setting a class attribute through one object
✗ Wrong
class Server:
status = "online"
web = Server()
db = Server()
web.status = "offline"
print(db.status)✓ Right
class Server:
status = "online"
web = Server()
db = Server()
Server.status = "offline"
print(db.status)web.status = ... creates a new attribute on web only and shadows the class one. To change the value for everyone, assign through the class name.
Counting with self.count += 1
✗ Wrong
class Visitor:
count = 0
def __init__(self, name):
self.name = name
self.count += 1
Visitor("Asha")
Visitor("Ben")
print(Visitor.count)✓ Right
class Visitor:
count = 0
def __init__(self, name):
self.name = name
Visitor.count += 1
Visitor("Asha")
Visitor("Ben")
print(Visitor.count)self.count += 1 reads the class value and stores the result on the object, so each visitor ends up with its own count of 1 and the class counter never changes. Use the class name instead.
A list in the class body shared by every object
✗ Wrong
class ShoppingCart:
items = []
def add(self, item):
self.items.append(item)
mine = ShoppingCart()
yours = ShoppingCart()
mine.add("milk")
print(yours.items)✓ Right
class ShoppingCart:
def __init__(self):
self.items = []
def add(self, item):
self.items.append(item)
mine = ShoppingCart()
yours = ShoppingCart()
mine.add("milk")
print(yours.items)The list is created once, when the class is defined, and every cart appends to that same list. Create it in __init__ so each cart gets its own.
Reading a class constant without self. or the class name
✗ Wrong
class Circle:
PI = 3.14159
def __init__(self, radius):
self.radius = radius
def area(self):
return PI * self.radius ** 2
print(Circle(2).area())✓ Right
class Circle:
PI = 3.14159
def __init__(self, radius):
self.radius = radius
def area(self):
return self.PI * self.radius ** 2
print(Circle(2).area())A name written in the class body is not a variable you can use inside methods. Reach it through the object (self.PI) or the class (Circle.PI).
Looking for a class attribute in vars(obj)
✗ Wrong
class Car:
wheels = 4
def __init__(self, model):
self.model = model
van = Car("Van")
print(vars(van)["wheels"])✓ Right
class Car:
wheels = 4
def __init__(self, model):
self.model = model
van = Car("Van")
print(van.wheels)
print("wheels" in vars(van), "wheels" in vars(Car))vars(obj) lists only the object's own attributes, and wheels lives on the class. Read it normally with van.wheels, or look in vars(Car).
9Python Class Attributes: exercises
Write your answer, then press Check. The checks run in your browser.
1. Count the robots
Write code Easy ✓ CompletedThe class Robot should keep track of how many robots have been built.
- Add a class attribute
builtthat starts at0. - Make
__init__add one to it for every new robot. Change the shared counter through the class name, not throughself. - Print
Robot.builtat the end of the program.
class Robot:
# 1. add a class attribute called built that starts at 0
def __init__(self, name):
self.name = name
# 2. add one to the shared counter, using the class name
r2 = Robot("R2")
r3 = Robot("R3")
print(r2.name, r3.name)
# 3. print how many robots were built
built = 0 directly under class Robot: (not inside __init__). Inside __init__, use Robot.built += 1.class Robot:
built = 0
def __init__(self, name):
self.name = name
Robot.built += 1
r2 = Robot("R2")
r3 = Robot("R3")
print(r2.name, r3.name)
print(Robot.built)
2. Predict the badge colours
Predict the output Medium ✓ CompletedOne badge changes its own colour, then the class changes its colour. Type exactly what the program prints.
class Badge:
color = "blue"
a = Badge()
b = Badge()
a.color = "red"
Badge.color = "green"
print(a.color, b.color, Badge.color)
b.color = "gold"
Badge.color = "black"
print(a.color, b.color, Badge.color)
a.color = "red", a keeps its own colour no matter what the class says. Track which objects have their own color at each print.red green green
red gold black
2 more exercises → 6 more examples
Questions learners ask
What is the difference between a class attribute and an instance attribute in Python?
A class attribute is written in the class body and stored once on the class, so every object shares it. An instance attribute is stored on one object, usually with self.name = ... in init, so each object has its own value. When you read obj.name, Python checks the object first and then the class.
Why does my class counter stay at 0 when I write self.count += 1?
self.count += 1 means self.count = self.count + 1. Python reads the class value, adds one, and then stores the result on the object, creating a new instance attribute that hides the class one. The class counter never changes. Write ClassName.count += 1 to update the shared value.
Are class attributes the same as static variables?
They play the same role: one value shared by all objects of the class. Python has no static keyword, so you simply write the variable in the class body. The difference to watch for is that assigning through an object (obj.x = 1) creates a separate instance attribute instead of changing the shared one.
Further reading
10Next lesson
Up nextPython Class Methods and Static MethodsA class method receives the class itself (cls) instead of one object, which makes it ideal for alternative constructors…Related:
Python Classes and ObjectsPython Class Methods and Static MethodsPython Encapsulation: Public, Protected and Private
← Python Classes and Objects · All Object-Oriented Python lessons · Practise in the IDE