Welcome to the Day 12 project in 30 Days of Python! We're going to create a program that can store and display books from a user's reading list.

Down below you'll find a full project brief as well as a walkthrough for our solution. Remember your solution doesn't have to match ours exactly!

The brief

For this project the application needs to have the following functionality:

  • Users should be able to add a book to their reading list by providing a book title, an author's name, and a year of publication.
  • The program should store information about all of these books in a Python list.
  • Users should be able to display all the books in their reading list, and these books should be printed out in a user-friendly format.
  • Users should be able to select these options from a text menu, and they should be able to perform multiple operations without restarting the program. You can see an example of a working menu in the post on while loops (day 8).

This project is a bit larger than the ones we've tackled before, so make sure you tackle it one piece at a time.

Something to note with this project is that because the books in the reading list are stored in a Python list, when the program ends, the reading list data will be lost.

In a couple of days, in Day 14, we will come back to this project and expand it, making sure we don't lose data when the program ends!

Good luck and I hope you have fun!

Our walkthrough

Our complete walkthrough is below, but if you prefer a video version check it out here:

Project Walkthrough
Reading List
15:50

Our first step when developing this program would be to create a place to store our reading list. At the moment this is going to be a Python list:

reading_list = []

Then, we might also want to define the menu that users will interact with. Doing this early gives us some structure as well to continue coding. By writing the menu out, it'll be clear in our heads what options users have to pick from:

reading_list = []
menu_prompt = """Please enter one of the following options:

- 'a' to add a book
- 'l' to list the books
- 'q' to quit

What would you like to do? """

Now we've got this, we can go ahead and start writing the menu. We'll be using a while loop and if statements for this:

reading_list = []
menu_prompt = """Please enter one of the following options:

- 'a' to add a book
- 'l' to list the books
- 'q' to quit

What would you like to do? """

selected_option = input(menu_prompt).strip().lower()

while selected_option != "q":
    if selected_option == "a":
        print("Adding...")
    elif selected_option == "l":
        print("Displaying...")
    else:
        print(f"Sorry, '{selected_option}' isn't a valid option.")

    # Allow the user to change their selection at the end of each iteration
    selected_option = input(menu_prompt).strip().lower()

Notice that we've coded the loop and the if statements, but at the moment those aren't doing much apart from printing what the user chose.

At this point we should run our program to make sure everything's working as expected!

Note that you could write the while loop like this if you preferred:

while True:
    selected_option = input(menu_prompt).strip().lower()

    if selected_option == "q":
        break
    elif selected_option == "a":
        print("Adding...")
    elif selected_option == "l":
        print("Displaying...")
    else:
        print(f"Sorry, '{selected_option}' isn't a valid option.")	

That way we reduce duplication of the selected_option variable.

It's totally up to you though, both are perfectly valid. Go for the one that seems most natural to you!

Frequently when making these menus, we will want to create a function for each branch of the if statement.

That's because each branch does its own thing, and creating a function to handle that case can make our program easier to read.

We'll do this now:

reading_list = []
menu_prompt = """Please enter one of the following options:

- 'a' to add a book
- 'l' to list the books
- 'q' to quit

What would you like to do? """

selected_option = input(menu_prompt).strip().lower()


def add_book():
    print("Adding...")


def show_books():
    print("Displaying...")


while selected_option != "q":
    if selected_option == "a":
        add_book()
    elif selected_option == "l":
        show_books()
    else:
        print(f"Sorry, '{selected_option}' isn't a valid option.")

    # Allow the user to change their selection at the end of each iteration
    selected_option = input(menu_prompt).strip().lower()

The benefit of this approach is that now we can just work inside each function, knowing they'll operate independently and separately.

Plus, it makes our loop shorter than having all the code in there!

To add new books, we'll ask the user for the book details. Then we'll put them in a dictionary and append them to the reading list:

def add_book():
    title = input("Title: ").strip().title()
    author = input("Author: ").strip().title()
    year = input("Year of publication: ").strip()
    
    new_book = {
        "title": title,
        "author": author,
        "year": year
    }
    
    reading_list.append(new_book)

When the function runs inside the loop, it will ask the user for the three pieces of information that we need for each book. Then it creates a dictionary, and stores it inside reading_list.

Note that we could create the dictionary and append it in the same line. That can sometimes be clearer:

def add_book():
    title = input("Title: ").strip().title()
    author = input("Author: ").strip().title()
    year = input("Year of publication: ").strip()
    
    reading_list.append({
        "title": title,
        "author": author,
        "year": year
    })

Try running the project now and adding a new book!

It should all be working, and the book will be added.

However, we don't yet have a way of displaying the books in the reading list. Let's work on that next:

def show_books():
    print("Displaying...")

Here we want to go through the reading_list variable, printing information from each book.

We can just use a for loop and print the properties in the dictionary using an f-string:

def show_books():
    for book in reading_list:
        print(f"{book['title']}, by {book['author']} ({book['year']})")

Alternatively we could use unpacking and the dictionary's .values() method to make this code a bit clearer:

def show_books():
    for book in reading_list:
        title, author, year = book.values()
        print(f"{title}, by {author} ({year})")

I think for something like this piece of code, creating separate variables might be a bit overkill. I just wanted to show you that you can do this! If it makes it clearer or more readable for you, go for it!

At the moment our code looks like this:

reading_list = []
menu_prompt = """Please enter one of the following options:

- 'a' to add a book
- 'l' to list the books
- 'q' to quit

What would you like to do? """

selected_option = input(menu_prompt).strip().lower()


def add_book():
    title = input("Title: ").strip().title()
    author = input("Author: ").strip().title()
    year = input("Year of publication: ").strip()
    
    reading_list.append({
        "title": title,
        "author": author,
        "year": year
    })


def show_books():
    for book in reading_list:
        title, author, year = book.values()
        print(f"{title}, by {author} ({year})")


while selected_option != "q":
    if selected_option == "a":
        add_book()
    elif selected_option == "l":
        show_books()
    else:
        print(f"Sorry, '{selected_option}' isn't a valid option.")

    selected_option = input(menu_prompt).strip().lower()

We can make one more improvement to this.

If we call show_books(), but reading_list is empty, the function will do nothing. The loop will not run at all.

So instead of that, let's print a nice message if reading_list is empty!

We'll do this in the loop. The show_books() function should be concerned with displaying books, and not with displaying books or a message if there are no books.

Keeping your functions doing one thing only is a great way to make your code simpler!

In the loop we'll add an if statement to check the reading list is not empty:

...
    elif selected_option == "l":
        if reading_list:
            show_books()
        else:
            print("Your reading list is empty.")
...

Remember that doing if reading_list passes reading_list to the bool() function. That evaluates to False if reading_list is empty. Otherwise, it evaluates to True.

And with this, we're done with this project!

There are many more improvements we could add:

  1. We could use arguments, parameters, and return values (covered tomorrow) so that our functions never interact directly with the global variable, reading_list. Functions that don't interact with global variables are usually simpler to think about and make the code less intertwined.
  2. We could allow the user to search through the books by e.g. author. Then we would only display books by that author.
  3. We could permanently store the contents of reading_list into a file every time we make a change, so that the user's reading list isn't lost when the program ends.

We will be making these changes in Day 14, so stay tuned for that! Until then, feel free to try to implement that yourselves if you'd like a challenge!

Thank you for reading, and I'll see you tomorrow!