Python

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

One class attribute, shared until it is reassignedpython-3.12
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)}
What to look for

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 tests

The 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"])
Loading editor…

Sign up to check the hidden tests and save your progress. Sign up