a = 1.511 Classes
11.1 Object Oriented Programming
Python is what is called an object oriented programming language. This means that “objects” in Python represent both data and methods that operate on that data.
Let’s take a very simple example:
When we do this we are not only binding the value 1.5 to a. We are also creating a floating-point object which comes with several methods. For example, one method it comes with is to check if the value is an integer:
a.is_integer()False
In Python, a class is an object constructor. It is a blueprint for creating objects. By writing a class of our own, we can create objects that have a specific structure and methods.
11.2 Defining a Class
We will use a running example of a bank account to illustrate this concept. At its simplest level, a bank account has an account holder and a balance (the amount of money in the account).
We can create a simple class of a bank account as follows:
class BankAccount:
def __init__(self, account_holder, opening_balance=0):
self.account_holder = account_holder
self.balance = opening_balanceThe keyword class begins the class definition, followed by the class name followed with a :. The structure is like definining a function, but using class instead of def. By convention, class names use capital letters, so we write BankAccount rather than bank_account.
When defining a class, we always definte one function inside called __init__(). This initializes an object. The __init__ method runs automatically whenever an object of a certain class is created. In this example, whenever a new bank account is created. It sets the object’s initial data.
The parameter self refers to the particular object being created. The expressions self.account_holder and self.balance are the object’s attributes. They store information belonging to that account.
11.2.1 Creating Objects
Let’s create two objects with the class BankAccount:1
account1 = BankAccount("Ailill", 1005)
account2 = BankAccount("Medb", 1000)We can see these have the class BankAccount:
type(account1)__main__.BankAccount
We can access the name of the account holder for account 1 with:
account1.account_holder'Ailill'
and their balance with:
account1.balance1005
We can also adjust attributes of an object directly with:
account2.balance = 1100
account2.balance1100
11.3 Adding Methods to a Class
We can easily add methods to a class by just defining functions under the initial __init__ function. We need to use self as the first argument for these functions. Let’s create deposit and withdraw methods which each take an amount as the argument:
class BankAccount:
def __init__(self, account_holder, opening_balance=0):
self.account_holder = account_holder
self.balance = opening_balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if amount <= self.balance:
self.balance -= amount
else:
print("Insufficient funds")Because we have updated the class with these methods, we need to recreate our account objects so they get these methods:
account1 = BankAccount("Ailill", 1005)
account2 = BankAccount("Medb", 1000)Let’s deposit 100 in Ailill’s bank account and check the balance:
account1.deposit(100)
account1.balance1105
Notice that we didn’t need to provide something for self as an argument to deposit. The object we are applying the method to passes everything in self to the method (in this case, the account holder and the balance).
Now let’s try withdraw 600 twice from Medb’s bank account:
account2.withdraw(600)
account2.balance400
account2.withdraw(600)
account2.balanceInsufficient funds
400
11.4 Inheritance
The bank account we developed is just a standard current account which can do deposits and withdraws. Suppose we wanted to also create a savings account which can generate interest. We don’t have to create a new class from scratch, but rather we can create a new class that inherits the methods of the standard bank account and add only whatever extra functionality we need.
11.4.1 Savings Account
Let’s create a new class called SavingsAccount doing this:
class SavingsAccount(BankAccount):
def add_interest(self, rate):
self.balance *= (1 + rate / 100)We can create a new account using the SavingsAccount in a similar way to BankAccount, with the account holder and initial balance:
account3 = SavingsAccount("Ailill", 500)We can use all the methods from the standard BankAccount class, even though we didn’t need to redefine them here:
account3.deposit(500)
account3.balance1000
And we can use the additional functionality we added (applying interest). Let’s apply 1% interest:
account3.add_interest(1)
account3.balance1010.0
11.4.2 Checking Account
We can also create an new class inheriting methods from another, and overriding some of its methods. In addition, we can add additional arguments to this class.
For example, suppose we have a checking account that charges a fee for each withdrawal. The fee is a new argument when creating the function.
class CheckingAccount(BankAccount):
def __init__(self, owner, balance, fee):
super().__init__(owner, balance)
self.fee = fee
def withdraw(self, amount):
super().withdraw(amount + self.fee)Here the super() function gives the class access to methods and properties of the parent class (BankAccount). In __init__() we didn’t need to redefine it completely. The line super().__init__(owner, balance) runs the __init() function from the parent class and sets:
self.account_holder = account_holder
self.balance = opening_balance
We then only need to do the new step of setting self.fee = fee.
In the new withdraw function, we also use super(). Here we essentially re-use the withdraw() function from the parent class, but add the fee to it. It is equivalent to replacing it with the function:
def withdraw(self, amount):
if amount + self.fee <= self.balance:
self.balance -= amount + self.fee
else:
print("Insufficient funds")
But notice that this is just the original withdraw() function with amount replaced with amount + self.fee. So we can just reuse our old function and just add the fee to the amount. This is what super() allows us to do.
Let’s test it out with fee = 1:
account4 = CheckingAccount("Medb", 500, 1)
account4.deposit(200)
account4.balance700
account4.withdraw(100)
account4.balance599
The fee was applied! Notice that for bank accounts with the parent class BankAccount the fee is not applied:
account2.balance400
account2.withdraw(100)
account2.balance300
11.5 Key Terms
Now that we have seen examples of these various things, it’s time to recap all the terminology we have just seen:
- A class is an object constructor. It is blueprint for creating objects. Here,
BankAccountwas a class. - An object is a specific item created from a class. Here
account1is an object created from the classBankAccount. - An attribute stores information about an object. Here, account holder and balance are attributes of a bank account.
- A method defines an action that an object can perform. Here,
depositandwithdrawwere two methods we created. - The
__init__method sets the initial state of a new object. - The
selfparameter refers to the object on which a method is operating.
These two names are characters from the Irish mythological story the Táin Bó Cúailnge. King Ailill and Queen Medb had a quarrel over who had the most wealth (i.e. money in their bank account).↩︎