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
Try it yourself
2 visible tests · 2 hidden testsThe Team class below has the shared-mutable-class-attribute bug from
this lesson. Fix it so every Team() instance gets its own
members list, then implement add_all(names) to append every name
in names to self.members and return the resulting list.
entryPoint(names_a, names_b) creates two separate teams, adds
names_a to the first and names_b to the second, and returns
[team_a.members, team_b.members].
entryPoint(["Nino","Ana"], ["Luka"])entryPoint([], ["Mari","Giorgi"])
Sign up to check the hidden tests and save your progress. Sign up