Week 13: Writing Classes

Week 13 Goals

  • More understanding of writing classes

  • Understand how one class can use instances of another

Get Week 13 In-class Code

To copy over the week 13 in-class example programs, do the following (If you have trouble with either of these steps, ask a Ninja or your professor for help):

  1. Create a w13-more-classes subdirectory in your cs21/inclass directory, and cd into it:

    $ cd ~/cs21/inclass
    $ mkdir w13-more-classes
    $ cd w13-more-classes
    $ pwd
    /home/yourusername/cs21/inclass/w13-more-classes
  2. Copy over the week 13 files into your w13-more-classes subdirectory (check that they copied successfully copied by running ls:

    $ cp ~admin21/public/w13-more-classes/*.py ./
    $ ls
    bankaccount.py

Week 13 Files

  • bankaccount.py - defines and uses a BankAccount class

Relationships Between Classes

One of the powerful things about object-oriented programming and being able to define our own custom classes is that we can aggregate them into more complex structures.

In this example, we’ll create a class Playlist which represents a collection of Song objects.

First, here’s the definition of the Song class:

"""
Class that represents a song.
"""

class Song:

    def __init__(self, artist, title, duration, year):
        """
        Constructor. Creates a new instance of a Song.
        Parameters:
        * self: refers to this object
        * artist (str): the name of the person/group who recorded the song
        * title (str): the name of the song
        * duration (int): the length of the song in seconds
        * year (int): the year in which the song was recorded
        """
        self.artist = artist
        self.title = title
        self.duration = duration
        self.year = year

    def getArtist(self):
        return self.artist

    def getTitle(self):
        return self.title

    def getDuration(self):
        return self.duration

    def getYear(self):
        return self.year

Now we can create a class Playlist that includes a list of Song objects as one of its instance variables:

"""
Class that represents a playlist consisting of multiple songs.

The fields are as follows:
* name (str): the name given to the playlist
* songs (list): a list of Song objects
"""

from song import *

class Playlist:

    # TODO: define a constructor that allows the caller to set the name
    # The list of songs should be initialized to an empty list
    def __init__(self, name):
        """
        Constructor to create a Playlist with the given name.
        The list of songs is initialized to an empty list.
        """
        self.name = name
        self.songs = []

    def getName(self):
        return self.name

    def getSongs(self):
        return self.songs

    def addSong(self, song):
        """
        Adds a song to the playlist.
        Parameters:
         * self: this object
         * song (Song): object to add to list of songs
        Returns: None. The Song is added to the list of songs
        """
        self.songs.append(song)

    def getTotalDuration(self):
        """
        Calculates the total duration of all songs in the playlist.
        Parameter:
         * self: this object
        Returns: sum of durations of all songs in list (float)
        """
        total = 0
        for song in self.songs:
            total = total + song.getDuration()
        return total

Now we can write code that uses these classes:

def main():
    s1 = Song("Carly Rae Jepsen", "Call Me Maybe", 193, 2012)
    s2 = Song("Taylor Swift", "Shake It Off", 219, 2014)
    p = Playlist("Happy music")
    p.addSong(s1)
    p.addSong(s2)
    print(p.getTotalDuration())

Having One Class Use an Instance of the Same Class

In the previous example, we saw that one class (Playlist) used an instance of another (Song).

But we can also have one class use an instance of the same class.

For instance, let’s say we have a BankAccount class like this:

"""
Class to represent a bank account.
"""

class BankAccount:

    def __init__(self, name):
        """
        Constructor to create new BankAccount instance.
        In addition to initializing the name, the BankAccount's
        balance is initialized to 0
        """
        self.name = name
        self.balance = 0.0

    def getName(self):
        return self.name

    def getBalance(self):
        return self.balance

    def deposit(self, amount):
        """
        Attempts to make a deposit to the BankAccount.
        Increases the balance by the amount, assuming the amount is positive.
        Otherwise, does not modify the balance.
        Parameters:
        * self: refers to this object
        * amount (float): amount to be deposited
        Returns (boolean): whether or not the balance was modified.
        """
        if (amount <= 0):
            return False
        else:
            self.balance = self.balance + amount
            return True

    def withdraw(self, amount):
        """
        Attempts to withdraw from the BankAccount.
        Decreases the balance by the amount, assuming the amount is positive
        but is not greater than the balance (i.e. the resulting balance
        cannot be negative).
        Otherwise, does not modify the balance.
        Parameters:
        * self: refers to this object
        * amount (float): amount to be withdrawn
        Returns (boolean): whethe or not the balance was modified.
        """
        if (amount <= 0 or amount > self.balance):
            return False
        else:
            self.balance = self.balance - amount
            return True

    def transfer(self, amount, other):
        """
        Attempts to transfer money from this BankAccount to the other one.
        Will only do so if the amount is positive and is not greater than
        this object's balance.
        Otherwise, the balances are not modified.
        Parameters:
        * self: refers to this object
        * amount (float): amount to be transferred
        * other (BankAccount): account to which money should be transferred
        Returns (boolean): whethe or not transfer was made.
        """
        if (amount <= 0):
            return False
        elif (amount > self.balance):
            return False
        else:
            self.withdraw(amount)
            other.deposit(amount)
            return True

Note that the transfer method takes a parameter of another instance of the BankAccount class. This is okay, because this method can differentiate between the object on which it is called (self) and the one that is passed as an argument (other).

So now we can transfer money from one BankAccount to another like this:

def main():
    b1 = BankAccount("Bingo")
    b1.deposit(100)
    b2 = BankAccount("Bluey")
    b2.deposit(400)

    b1.transfer(50, b2) # transfers from b1 to b2

    print(b1.getBalance()) # Bingo should now have $50
    print(b2.getBalance()) # Bluey should now have $450