Classes, __init__, and the attributes that surprise people
Instance attributes against class attributes, and the mutable class attribute that behaves like a global.
After this lesson you can
- Write a class with __init__ setting instance attributes
- Explain the difference between a class attribute and an instance attribute
- Say why a mutable class attribute is shared across every instance
__init__ runs once, right after a new instance is created, and sets up
that instance's own state.
class Account:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
return self.balance
self is the instance itself, passed automatically — account.deposit(10)
is really Account.deposit(account, 10). Every method needs self as
its first parameter to reach the instance's own attributes.
Instance attributes against class attributes
An attribute set on self inside __init__ belongs to that one
instance. An attribute set directly in the class body is shared by
every instance that does not shadow it.
class Player:
lives = 3 # class attribute — one copy, shared
def __init__(self, name):
self.name = name # instance attribute — one per player
a = Player("A")
b = Player("B")
a.lives -= 1
a.lives, b.lives # (2, 3) — assignment on a created a's own attribute
a.lives -= 1 looks like it mutates the shared value, but it does not:
reading a.lives first falls through to the class attribute (3), and
the assignment then creates a brand-new instance attribute on a
alone, which is why b.lives is untouched.
Try it
class Player: lives = 3 def __init__(self, name): self.name = name def run(): a = Player("A") b = Player("B") before = (a.lives, b.lives) a.lives -= 1 after = (a.lives, b.lives) return {"before": list(before), "after": list(after)}The trap: a mutable class attribute
The falling-through behaviour above only protects you for reassignment. A mutable class attribute that gets mutated in place, rather than reassigned, has no such protection — because there is only ever one of it.
class Team:
members = [] # one list, shared by the class
def add(self, name):
self.members.append(name) # mutates the shared list
a = Team()
b = Team()
a.add("Nino")
b.members # ["Nino"] — b sees a's addition; there was only ever one list
self.members.append(...) never creates an instance attribute the way
self.members = [...] would — it looks up the shared list and mutates
it. The fix is the same one from the mutable-default-argument lesson:
give each instance its own list, explicitly, in __init__.
class Team:
def __init__(self):
self.members = [] # a fresh list per instance
Common mistakes
- Declaring a list or dict in the class body and expecting one per instance. There is one, shared by all of them, as shown above.
- Leaving
selfout of a method's parameters. The call still passes the instance, so Python reports one argument more than the method accepts. - Creating an attribute for the first time in some method other than
__init__. Any instance on which that method has not run yet has no such attribute, and reading it raisesAttributeError.
class Counter: def __init__(self): self.count = 0 def bump(): # self is missing pass def start_timer(self): self.started = True # only exists once this has run def run(): c = Counter() try: c.bump() missing_self = "no error" except TypeError as e: missing_self = f"TypeError: {e}" try: late = c.started except AttributeError as e: late = f"AttributeError: {e}" c.start_timer() return {"missing_self": missing_self, "before_start": late, "after_start": c.started}What interviewers ask
- What is the difference between a class attribute and an instance
attribute? Lookup goes instance first, then class; assigning through
selfalways writes to the instance, while mutating in place changes whichever object the lookup found. - What is
self, and why is it explicit? The instance the method was called on;obj.method(x)isType.method(obj, x), nothing more. - When is a class attribute the right tool? Constants and defaults shared by every instance, ideally immutable ones.
Exercise · Pro
A team roster that is never shared by accident
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade