Classes, __init__, methods & self
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
__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.
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.
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.
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) # 150Apply
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 inself.balance.deposit(self, amount)addsamounttoself.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)startsself.countat0.increment(self)adds1toself.count.
run(3) should return 3.
2 hints and 3 automated checks are waiting in the workspace.