Previous Lecture Lecture 3 Next Lecture

Lecture 3, Tue 04/09

Python Objects and Classes

Plan for today

Object Oriented Programming: Model Real-world Properties

Example: built-in method for a dictionary vs. enumerate() function:

my_dict = {"jan": 1, "feb": 2}
for index, month in enumerate(my_dict):
    print(index, month)

Output:

0 jan
1 feb

Same function using a string:

my_string = "Python is awesome"
for index, char in enumerate(my_string):
    print(index, char)

Using a class method involves calling the method using a dot notation:

my_dict.get("march")
my_dict.get("feb")
2
my_string.get("Python")
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    my_string.get("Python")
AttributeError: 'str' object has no attribute 'get'

Defining a new class

class Song:

    def set_title(self, new_title):
        self.title = new_title

    def set_genre(self, new_genre):
        self.genre = new_genre

    def set_duration(self, new_duration):
        self.duration = new_duration

    def get_title(self):
        return self.title

    def get_genre(self):
        return self.genre

    def get_duration(self):
        return self.duration

When calling the method, we need to make sure to use parentheses, otherwise, Python will display the info about the method itself as an object:

song1.get_title()
'Some song'

song1.get_genre()
'Techno'

song1.get_genre
<bound method Song.get_genre of <__main__.Song object at 0x1034e7c50>>
type(song1.get_genre)
<class 'method'>

The self parameter

Every method of any class should have self as its first parameter; self refers to the object of that class that will be calling that method - Python implicitly passes it into the method call.

If we try to re-write our method call to pass the object of that class as an argument, we’ll get different errors:

song1.set_title(song1, "Some song")
TypeError: Song.set_title() takes 2 positional arguments but 3 were given

set_title(song1, "Some song")
NameError: name 'set_title' is not defined

The way that Python calls the constructor in the following two lines is equivalent:

Two forms of a constructor

To create a new object of our class, we can use the following 3 strategies:

Testing our code

We customarily put the tests into a separate file, which we need to run to verify that the code works as expected.

from Song import Song

song1 = Song() # instantiating a new song object
assert song1.get_genre() == ""
assert song1.get_title() == ""

song1.set_title("Some song")
song1.set_genre("Techno")

assert song1.get_genre() == "Techno"
assert song1.get_title() == "Some song"

song2 = Song("Another title", "Jazz")
assert song2.get_genre() == "Jazz"
assert song2.get_title() == "Another title"

print("All tests pass!") # will display if everything worked

We can also test it interactively by using the IDLE shell:

print(song1.get_title())
Some song
print(song2.get_title())
Another title
song3 = Song()
print(song3.get_title())
None
print(song3.get_genre())
None

Note that the default parameter values allow us to use positional arguments and initialize some of them:

song3 = Song("Hello")
print(song3.get_title())
Hello
print(song3.get_genre())
None

Displaying the object values

To be able to see the parameters displayed in a nicer form, we can define a method to help us:

    def info(self):
        return f"Title: {self.title}\nGenre: {self.genre}"

The method returns a string, so if we use the IDLE shell to see the return value, we’ll see the newline character \n; if we print the returned string, the newline will be converted into part of the output:

song1.info()
'Title: Some song\nGenre: Techno'

print(song1.info())
Title: Some song
Genre: Techno

print(song2.info())
Title: Another title
Genre: Jazz

Constructor classes

We can now begin using our custom classes as part of our other classes.

Let’s create a playlist of our songs, organized by the songs’ genres.

from Song import Song 

class Playlist:
    '''
    Class representing a collection of songs.
    Songs are organized by a dictionary where
    the key is the song genre and the corresponding
    value is a list of songs with that genre.
    '''
    def __init__(self):
        self.db = {}

Let’s add a new song to this database:

def add_song(self, new_song):
		# If a genre doesn't exist…
genre =  new_song.get_genre()
		if self.db.get(genre) == None:
			# new entry in the dictionary; its value is a list
			self.db[genre] = [new_song]
		elif not new_song in self.db.get(genre):
			self.db[genre].append(new_song)