{"id":"objects-as-attributes-none","slug":"none","title":"None: no object","text":"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.\n\n### A search that may find nothing\n\nA method that looks for an object can return `None` when there's no match:\n\n```python\nclass Song:\n    def __init__(self, title, seconds):\n        self.title = title\n        self.seconds = seconds\n\n    def __str__(self):\n        return f\"{self.title} ({self.seconds} s)\"\n\n\nclass Playlist:\n    def __init__(self):\n        self.songs = []\n\n    def add(self, song):\n        self.songs.append(song)\n\n    def find(self, title):\n        for song in self.songs:\n            if song.title == title:\n                return song\n        return None\n\n\nmix = Playlist()\nmix.add(Song(\"Neon Rain\", 187))\nprint(mix.find(\"Neon Rain\"))\nprint(mix.find(\"Blue Hour\"))\n```\n\n```output\nNeon Rain (187 s)\nNone\n```\n\nThe `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.\n\n### The error you will meet\n\nThe 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:\n\n```python\nclass Song:\n    def __init__(self, title, seconds):\n        self.title = title\n        self.seconds = seconds\n\n\nclass Playlist:\n    def __init__(self):\n        self.songs = []\n\n    def find(self, title):\n        for song in self.songs:\n            if song.title == title:\n                return song\n        return None\n\n\nmix = Playlist()\nsong = mix.find(\"Blue Hour\")\nprint(song.seconds)\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 20, in <module>\n    print(song.seconds)\n          ^^^^^^^^^^^^\nAttributeError: 'NoneType' object has no attribute 'seconds'\n```\n\n`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.\n\n### Check before you use it\n\nTest 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:\n\n```python\nsong = mix.find(\"Blue Hour\")\nif song is None:\n    print(\"Not on the playlist\")\nelse:\n    print(song.seconds)\n```\n\n### An attribute that starts out empty\n\nAn attribute can be `None` until it gets an object. A parking spot starts without a car:\n\n```python\nclass Car:\n    def __init__(self, plate):\n        self.plate = plate\n\n\nclass ParkingSpot:\n    def __init__(self, number):\n        self.number = number\n        self.car = None\n\n    def park(self, car):\n        if self.car is not None:\n            return False\n        self.car = car\n        return True\n\n    def __str__(self):\n        if self.car is None:\n            return f\"Spot {self.number}: free\"\n        return f\"Spot {self.number}: {self.car.plate}\"\n\n\nspot = ParkingSpot(7)\nprint(spot)\nprint(spot.park(Car(\"ABC-123\")))\nprint(spot.park(Car(\"XYZ-789\")))\nprint(spot)\n```\n\n```output\nSpot 7: free\nTrue\nFalse\nSpot 7: ABC-123\n```\n","task":"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:\n\n```\nLea: 040 123 4567\nMax is not in the phone book\n```\n","kind":"fill","seed":"class Contact:\n    def __init__(self, name, number):\n        self.name = name\n        self.number = number\n\n\nclass PhoneBook:\n    def __init__(self):\n        self.contacts = []\n\n    def add(self, contact):\n        self.contacts.append(contact)\n\n    def find(self, name):\n        for contact in self.contacts:\n            if contact.name == name:\n                return contact\n        return [[None]]\n\n\nbook = PhoneBook()\nbook.add(Contact(\"Lea\", \"040 123 4567\"))\nbook.add(Contact(\"Sam\", \"050 765 4321\"))\n\nfor name in [\"Lea\", \"Max\"]:\n    contact = book.find(name)\n    if contact [[is None‖== None]]:\n        print(f\"{name} is not in the phone book\")\n    else:\n        print(f\"{name}: {contact.number}\")\n","solution":"class Contact:\n    def __init__(self, name, number):\n        self.name = name\n        self.number = number\n\n\nclass PhoneBook:\n    def __init__(self):\n        self.contacts = []\n\n    def add(self, contact):\n        self.contacts.append(contact)\n\n    def find(self, name):\n        for contact in self.contacts:\n            if contact.name == name:\n                return contact\n        return None\n\n\nbook = PhoneBook()\nbook.add(Contact(\"Lea\", \"040 123 4567\"))\nbook.add(Contact(\"Sam\", \"050 765 4321\"))\n\nfor name in [\"Lea\", \"Max\"]:\n    contact = book.find(name)\n    if contact is None:\n        print(f\"{name} is not in the phone book\")\n    else:\n        print(f\"{name}: {contact.number}\")\n","hints":["When the loop finds no contact with the name, `find` should return the value that means \"no object\".","That value is `None`, and you test for it with `is None`: `if contact is None:`."],"tests":[{"name":"Output","stdin":"","expect":"Lea: 040 123 4567\nMax is not in the phone book","hidden":false}],"require":[],"forbid":[],"more":[{"task":"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.\n\nFor example, this code:\n\n```python\ninbox = Inbox()\nprint(inbox.first_unread())\ninbox.add(Message(\"Ana\", \"Lunch?\"))\ninbox.add(Message(\"Ben\", \"Meeting at 3\"))\ninbox.messages[0].read = True\nprint(inbox.first_unread())\n```\n\nprints:\n\n```\nNone\nBen: Meeting at 3\n```\n","kind":"code","seed":"class Message:\n    def __init__(self, sender, text):\n        self.sender = sender\n        self.text = text\n        self.read = False\n\n    def __str__(self):\n        return f\"{self.sender}: {self.text}\"\n\n\nclass Inbox:\n    def __init__(self):\n        self.messages = []\n\n    def add(self, message):\n        self.messages.append(message)\n\n    def first_unread(self):\n        pass\n\n\nif __name__ == \"__main__\":\n    inbox = Inbox()\n    print(inbox.first_unread())\n    inbox.add(Message(\"Ana\", \"Lunch?\"))\n    inbox.add(Message(\"Ben\", \"Meeting at 3\"))\n    inbox.messages[0].read = True\n    print(inbox.first_unread())\n","solution":"class Message:\n    def __init__(self, sender, text):\n        self.sender = sender\n        self.text = text\n        self.read = False\n\n    def __str__(self):\n        return f\"{self.sender}: {self.text}\"\n\n\nclass Inbox:\n    def __init__(self):\n        self.messages = []\n\n    def add(self, message):\n        self.messages.append(message)\n\n    def first_unread(self):\n        for message in self.messages:\n            if not message.read:\n                return message\n        return None\n\n\nif __name__ == \"__main__\":\n    inbox = Inbox()\n    print(inbox.first_unread())\n    inbox.add(Message(\"Ana\", \"Lunch?\"))\n    inbox.add(Message(\"Ben\", \"Meeting at 3\"))\n    inbox.messages[0].read = True\n    print(inbox.first_unread())\n","hints":["Go through `self.messages` in order, and return the first message whose `read` is `False`.","Returning inside the loop ends the method right away, so the first unread message is the one returned.","After the loop, `return None`: if the method gets that far, there was no unread message."],"tests":[{"name":"Test 1","stdin":"","call":"inbox = Inbox()\nprint(inbox.first_unread())\ninbox.add(Message(\"Ana\", \"Lunch?\"))\ninbox.add(Message(\"Ben\", \"Meeting at 3\"))\ninbox.messages[0].read = True\nprint(inbox.first_unread())\n","expect":"None\nBen: Meeting at 3","hidden":false},{"name":"Test 2","stdin":"","call":"inbox = Inbox()\ninbox.add(Message(\"Kim\", \"Hi\"))\ninbox.add(Message(\"Lou\", \"Bye\"))\ninbox.add(Message(\"Max\", \"Ok\"))\nprint(inbox.first_unread())\nfound = inbox.first_unread()\nprint(found is inbox.messages[0])\nfor message in inbox.messages:\n    message.read = True\nprint(inbox.first_unread())\n","expect":"Kim: Hi\nTrue\nNone","hidden":true},{"name":"Test 3","stdin":"","call":"inbox = Inbox()\ninbox.add(Message(\"A\", \"one\"))\ninbox.add(Message(\"B\", \"two\"))\ninbox.add(Message(\"C\", \"three\"))\ninbox.messages[0].read = True\ninbox.messages[1].read = True\nprint(inbox.first_unread())\n","expect":"C: three","hidden":true}],"require":[],"forbid":[]},{"task":"A kennel at a dog hotel has room for one dog at a time. Write the class `Kennel`:\n\n- The constructor takes the kennel's number. A new kennel is empty: its attribute `dog` is `None`.\n- `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`.\n- `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`.\n- `__str__(self)` returns `Kennel 3: empty` or, with a dog in it, `Kennel 3: Rex`.\n\nFor example, this code:\n\n```python\nkennel = Kennel(3)\nprint(kennel)\nprint(kennel.check_in(Dog(\"Rex\")))\nprint(kennel.check_in(Dog(\"Bella\")))\nprint(kennel)\nleaving = kennel.check_out()\nprint(leaving.name)\nprint(kennel)\n```\n\nprints:\n\n```\nKennel 3: empty\nTrue\nFalse\nKennel 3: Rex\nRex\nKennel 3: empty\n```\n","kind":"code","seed":"class Dog:\n    def __init__(self, name):\n        self.name = name\n\n\nclass Kennel:\n    pass\n\n\nif __name__ == \"__main__\":\n    kennel = Kennel(3)\n    print(kennel)\n    print(kennel.check_in(Dog(\"Rex\")))\n    print(kennel.check_in(Dog(\"Bella\")))\n    print(kennel)\n    leaving = kennel.check_out()\n    print(leaving.name)\n    print(kennel)\n","solution":"class Dog:\n    def __init__(self, name):\n        self.name = name\n\n\nclass Kennel:\n    def __init__(self, number):\n        self.number = number\n        self.dog = None\n\n    def check_in(self, dog):\n        if self.dog is not None:\n            return False\n        self.dog = dog\n        return True\n\n    def check_out(self):\n        leaving = self.dog\n        self.dog = None\n        return leaving\n\n    def __str__(self):\n        if self.dog is None:\n            return f\"Kennel {self.number}: empty\"\n        return f\"Kennel {self.number}: {self.dog.name}\"\n\n\nif __name__ == \"__main__\":\n    kennel = Kennel(3)\n    print(kennel)\n    print(kennel.check_in(Dog(\"Rex\")))\n    print(kennel.check_in(Dog(\"Bella\")))\n    print(kennel)\n    leaving = kennel.check_out()\n    print(leaving.name)\n    print(kennel)\n","hints":["In `__init__`, set `self.dog = None`, and check `self.dog is None` wherever you need to know whether the kennel is empty.","`check_out` has to return the dog after emptying the kennel, so keep the dog in a local variable first: `leaving = self.dog`, then `self.dog = None`, then `return leaving`.","When the kennel is already empty, `self.dog` is `None`, so the same three lines return `None` with no extra `if`."],"tests":[{"name":"Test 1","stdin":"","call":"kennel = Kennel(3)\nprint(kennel)\nprint(kennel.check_in(Dog(\"Rex\")))\nprint(kennel.check_in(Dog(\"Bella\")))\nprint(kennel)\nleaving = kennel.check_out()\nprint(leaving.name)\nprint(kennel)\n","expect":"Kennel 3: empty\nTrue\nFalse\nKennel 3: Rex\nRex\nKennel 3: empty","hidden":false},{"name":"Test 2","stdin":"","call":"kennel = Kennel(12)\nprint(kennel.check_out())\nprint(kennel.dog)\nprint(kennel.check_in(Dog(\"Luna\")))\nprint(kennel)\nprint(kennel.check_out().name)\nprint(kennel.check_out())\nprint(kennel.check_in(Dog(\"Max\")))\nprint(kennel)\n","expect":"None\nNone\nTrue\nKennel 12: Luna\nLuna\nNone\nTrue\nKennel 12: Max","hidden":true}],"require":[],"forbid":[]}]}