Python Arena
Step 4 of 4

None: no object

Sometimes there is no object to give. A search finds nothing, or a parking spot has no car in it yet. For that, Python has the special value None, which means "nothing here". You have met it before: it's what a function returns when it has no return. You can also return it, or store it, on purpose.

A search that may find nothing

A method that looks for an object can return None when there's no match:

class Song:
    def __init__(self, title, seconds):
        self.title = title
        self.seconds = seconds

    def __str__(self):
        return f"{self.title} ({self.seconds} s)"


class Playlist:
    def __init__(self):
        self.songs = []

    def add(self, song):
        self.songs.append(song)

    def find(self, title):
        for song in self.songs:
            if song.title == title:
                return song
        return None


mix = Playlist()
mix.add(Song("Neon Rain", 187))
print(mix.find("Neon Rain"))
print(mix.find("Blue Hour"))
Output
Neon Rain (187 s)
None

The return song inside the loop ends the method as soon as the song is found. The return None after the loop only runs when the loop went through every song without finding it.

The error you will meet

The trouble starts when the caller forgets that the answer can be None, and uses it as if it were an object. With the same classes as above:

class Song:
    def __init__(self, title, seconds):
        self.title = title
        self.seconds = seconds


class Playlist:
    def __init__(self):
        self.songs = []

    def find(self, title):
        for song in self.songs:
            if song.title == title:
                return song
        return None


mix = Playlist()
song = mix.find("Blue Hour")
print(song.seconds)
Python stops with
Traceback (most recent call last):
  File "main.py", line 20, in <module>
    print(song.seconds)
          ^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'seconds'

NoneType is the type of None. So whenever you see 'NoneType' object has no attribute, the thing before the dot was None instead of the object you expected. Look for where that value came from: here, a search that found nothing.

Check before you use it

Test the answer with is None or is not None before you use it. There is only one None in the whole program, so is is the natural way to ask about it:

song = mix.find("Blue Hour")
if song is None:
    print("Not on the playlist")
else:
    print(song.seconds)

An attribute that starts out empty

An attribute can be None until it gets an object. A parking spot starts without a car:

class Car:
    def __init__(self, plate):
        self.plate = plate


class ParkingSpot:
    def __init__(self, number):
        self.number = number
        self.car = None

    def park(self, car):
        if self.car is not None:
            return False
        self.car = car
        return True

    def __str__(self):
        if self.car is None:
            return f"Spot {self.number}: free"
        return f"Spot {self.number}: {self.car.plate}"


spot = ParkingSpot(7)
print(spot)
print(spot.park(Car("ABC-123")))
print(spot.park(Car("XYZ-789")))
print(spot)
Output
Spot 7: free
True
False
Spot 7: ABC-123

Your task

Challenge 1 of 3

Fill in the blanks so that find returns None when there's no contact with the name, and the loop handles that case. The program should print:

Lea: 040 123 4567
Max is not in the phone book

Challenge 2

An inbox holds messages, and each message is either read or unread. Write the method first_unread(self) in the class Inbox. It returns the first message (the one added earliest) that hasn't been read yet, or None if every message has been read or the inbox is empty.

For example, this code:

inbox = Inbox()
print(inbox.first_unread())
inbox.add(Message("Ana", "Lunch?"))
inbox.add(Message("Ben", "Meeting at 3"))
inbox.messages[0].read = True
print(inbox.first_unread())

prints:

None
Ben: Meeting at 3

Challenge 3

A kennel at a dog hotel has room for one dog at a time. Write the class Kennel:

  • The constructor takes the kennel's number. A new kennel is empty: its attribute dog is None.
  • check_in(self, dog) puts the dog in the kennel and returns True, but only if the kennel is empty. If there's already a dog, nothing changes and it returns False.
  • check_out(self) takes the dog out and returns it (the Dog object), leaving the kennel empty. If the kennel is empty, it returns None.
  • __str__(self) returns Kennel 3: empty or, with a dog in it, Kennel 3: Rex.

For example, this code:

kennel = Kennel(3)
print(kennel)
print(kennel.check_in(Dog("Rex")))
print(kennel.check_in(Dog("Bella")))
print(kennel)
leaving = kennel.check_out()
print(leaving.name)
print(kennel)

prints:

Kennel 3: empty
True
False
Kennel 3: Rex
Rex
Kennel 3: empty