Contact Book in Python
Intermediate · Useful tools · about 60 minutes to build · 148 lines
Store names, phone numbers and email addresses, then find, change or remove any contact by searching for part of it.
The program
Press Run and answer its questions in the box that appears under the code.
Open it in the full editor to save it, share it and keep building.
Sample run
What the program printed, and what was typed (in yellow).
Contact book 1) Add 2) List 3) Search 4) Edit 5) Delete 6) Quit Choose 1-6: 1 Name: Asha Rao Phone: 98765 43210 Email (optional): asha@example.com Added Asha Rao. 1) Add 2) List 3) Search 4) Edit 5) Delete 6) Quit Choose 1-6: 1 Name: Ben Carter Phone: 12ab Use digits, spaces, + - ( ) and at least 7 digits. Phone: +44 20 7946 0958 Email (optional): Added Ben Carter. 1) Add 2) List 3) Search 4) Edit 5) Delete 6) Quit Choose 1-6: 1 Name: Chitra Nair Phone: 91234 56789 Email (optional): chitra@mail That does not look like an email address. Email (optional): chitra@example.org Added Chitra Nair. 1) Add 2) List 3) Search 4) Edit 5) Delete 6) Quit Choose 1-6: 3 Search for: ra # Name Phone Email 1 Asha Rao 98765 43210 asha@example.com 2 Chitra Nair 91234 56789 chitra@example.org 1) Add 2) List 3) Search 4) Edit 5) Delete 6) Quit Choose 1-6: 4 Search for the contact to edit: asha Editing Asha Rao. Press Enter to keep a value. Name [Asha Rao]: Phone [98765 43210]: 98765 00000 Email (optional) [asha@example.com]: Updated: Asha Rao 98765 00000 asha@example.com 1) Add 2) List 3) Search 4) Edit 5) Delete 6) Quit Choose 1-6: 5 Search for the contact to delete: ben Delete Ben Carter? (y/n): y Deleted Ben Carter. 1) Add 2) List 3) Search 4) Edit 5) Delete 6) Quit Choose 1-6: 2 # Name Phone Email 1 Asha Rao 98765 00000 asha@example.com 2 Chitra Nair 91234 56789 chitra@example.org 1) Add 2) List 3) Search 4) Edit 5) Delete 6) Quit Choose 1-6: 6 Goodbye.
How it is built
1. The Contact class
Each contact is an object with three attributes. matches lets one search look in the name, the phone and the email, and row formats the contact as a line of the table.
class Contact: """One person's name, phone number and email address.""" def __init__(self, name, phone, email): self.name = name self.phone = phone self.email = email def matches(self, text): """Return True if text appears in the name, phone or email.""" text = text.lower() return (text in self.name.lower() or text in self.phone or text in self.email.lower()) def row(self): """Return the contact as one line of the table.""" return f"{self.name:<16}{self.phone:<20}{self.email or '-'}"
2. Checking a phone number
A phone number may hold digits, spaces and + - ( ), and needs at least seven digits. Each check returns True or False, so it can be handed to ask_field as an argument.
def is_valid_phone(phone): """Digits, spaces, + - ( ) only, with at least MIN_PHONE_DIGITS digits.""" digits = sum(ch.isdigit() for ch in phone) return digits >= MIN_PHONE_DIGITS and all(ch in PHONE_CHARACTERS for ch in phone)
3. One question function for every field
The check arrives as the is_valid argument, so the same loop asks for a name, a phone or an email. When editing, current is shown in brackets and Enter keeps it.
def ask_field(label, is_valid, error, current=None): """Ask for one field; when editing, pressing Enter keeps the current value.""" hint = "" if current is None else f" [{current or 'none'}]" while True: text = input(f"{label}{hint}: ").strip() if text == "" and current is not None: return current if is_valid(text): return text print(f" {error}")
4. Searching and picking one
A search can match several people, so the function lists them and asks which one. With a single match it returns that contact straight away.
def find_one(contacts, action): """Search, let the user pick from the matches, and return one contact.""" text = input(f"Search for the contact to {action}: ").strip() found = search(contacts, text) if not found: print(" No contact matches that.") return None if len(found) == 1: return found[0] show_contacts(found) while True: choice = input(f"Which one (1-{len(found)})? ").strip() if choice.isdecimal() and 1 <= int(choice) <= len(found): return found[int(choice) - 1] print(f" Please type a number from 1 to {len(found)}.")
5. The menu
Editing passes the current values into fill_in, which hands back all three fields. Deleting asks for y before anyone is removed.
def main(): """Run the menu until the user quits.""" contacts = [] print("Contact book") while True: print() print(MENU) choice = input("Choose 1-6: ").strip() if choice == "1": contact = Contact(*fill_in()) contacts.append(contact) print(f" Added {contact.name}.") elif choice == "2": show_contacts(search(contacts, "")) # An empty search matches everyone elif choice == "3": show_contacts(search(contacts, input("Search for: ").strip())) elif choice == "4": contact = find_one(contacts, "edit") if contact: print(f" Editing {contact.name}. Press Enter to keep a value.") contact.name, contact.phone, contact.email = fill_in( contact.name, contact.phone, contact.email) print(f" Updated: {contact.row()}") elif choice == "5": contact = find_one(contacts, "delete") if contact: answer = input(f"Delete {contact.name}? (y/n): ").strip().lower() if answer == "y": contacts.remove(contact) print(f" Deleted {contact.name}.") else: print(" Nothing deleted.") elif choice == "6": print("Goodbye.") break else: print(" Please choose a number from 1 to 6.")
Make it bigger
- Save the contacts to a CSV file so the book survives a restart.
- Add a birthday to each contact and list the birthdays coming up this month.
- Refuse a second contact with the same phone number.
- Group the list by the first letter of each name.