Skip to main content

Classes, __init__, methods & self

Level 2: Level 2: Idiomsmedium12 minclassesinitmethodsself

Model state and behaviour together with a class.

Why bundle state with behaviour

Real systems track things that own both data and the rules for changing that data. A bank account has a balance plus rules for depositing. A shopping cart has items plus rules for adding them. You could keep a loose balance variable and pass it to standalone functions, but then nothing keeps the data and its rules together, and every caller has to remember which functions are allowed to touch it. A class ties the data and the operations that act on it into one named type, so the balance and the sanctioned way to change it travel together.

The mental model

A class is a template that describes a kind of object. Each object you build from it is an instance with its own copy of the data (its attributes). The functions defined inside the class are methods: the behaviour that acts on one instance's data.

class BankAccount:
    def __init__(self, balance):   # runs at construction
        self.balance = balance     # store data on THIS instance

    def deposit(self, amount):
        self.balance += amount     # read and update that data

account = BankAccount(100)   # __init__ runs, balance = 100
account.deposit(50)          # self is account; balance becomes 150
print(account.balance)       # 150
Check yourself
A learner writes def __init__(self, balance): return balance, hoping that BankAccount(100) evaluates to 100. What actually happens?

__init__ and self

__init__ is the initializer. Python calls it automatically when you write BankAccount(100), passing 100 in as balance. Its job is to set the instance's starting attributes. self is the instance the method is currently working on. It is the first parameter of every method, and self.balance is how you reach that specific instance's data. Two accounts each carry their own balance, and writing self.balance on one never touches the other.

Check yourself
Inside deposit a learner writes balance = self.balance + amount and nothing else. account starts at 100 and you call account.deposit(50). What is account.balance afterwards?

Pitfall: forgetting self

Inside a method, balance = amount creates a throwaway local variable that vanishes when the method returns. The attribute is never touched. You must write self.balance = amount, and read it back the same way with self.balance, not a bare balance. Every method also needs self as its first parameter, even when the method takes no other argument. In Practice, increment(self) has no extra parameters, but self still comes first.

Check yourself
A method is defined as def deposit(amount): ... with no self in the parameter list. You call account.deposit(50). What do you see?

Interview nuance: account.deposit(50) is shorthand. Python looks up deposit on the class and calls BankAccount.deposit(account, 50), binding account to self. That is why self is an ordinary first parameter and not magic: the dot syntax just passes the instance in for you. Knowing that a call is really Class.method(instance, ...) explains a common error too. If you omit self from a method's parameter list, calling it raises TypeError complaining that too many positional arguments were given, because Python still passes the instance.

Worked example (Python)
class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount

account = BankAccount(100)
account.deposit(50)
print(account.balance)   # 150

Apply

Your turn

The task this lesson builds to.

Finish the BankAccount class so the provided run driver works:

  • __init__(self, balance) stores the starting balance in self.balance.
  • deposit(self, amount) adds amount to self.balance.

run(100, 50) should return 150.

3 hints and 3 automated checks are waiting in the workspace.

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Implement a Counter class so the provided run driver works:

  • __init__(self) starts self.count at 0.
  • increment(self) adds 1 to self.count.

run(3) should return 3.

2 hints and 3 automated checks are waiting in the workspace.