def ask(message, restrict_to=None):
    """prints message and asks user for input.
    empty input is not allowed. if restrict_to is
    given, we only accept entries from this list"""
    
    if restrict_to is not None:
        assert isinstance(restrict_to, list)
    
    while True:
        answer = input(message).strip()
        if restrict_to is not None:
            if answer not in restrict_to:
                print("invalid input, try again")
            else:
                return answer
        elif not answer:
            print("invalid input, try again")
        else:
            return answer
        
        
def main_menu(entries):

    print("""
PHONEBOOK v1.0
==============

Already {num_entries} entries in the phone book

Please choose:

    1: add a new entry
    2: lookup a number
    3: show all entries
    0: exit
""".format(num_entries=len(entries)))
    return int(ask("what to do ? ", restrict_to = ["1", "2", "3", "0"]))


main_menu([])
PHONEBOOK v1.0
==============

Already 0 entries in the phone book

Please choose:

    1: add a new entry
    2: lookup a number
    3: show all entries
    0: exit

what to do ? ab
invalid input, try again
what to do ? 1
1

The simplest data structure would be a dictionary mapping names to phone numbers. To get a more "falt tolerant" lookup we simplify a name by creating a spelling tolerant fingerprint.

Then we map this fingerprint to a list of tuples. Every tuple is a pair of name and phone number

def fingerprint(word):
    """ inspired by https://www.wikiwand.com/en/Soundex """
    
    word = word.lower()
    head, tail = word[0], word[1:]
    for c in "aeioyuhvwlr.-":
        tail = tail.replace(c, "")
    for c in "fpv":
        tail = tail.replace(c, "b")
    for c in "gjkqsxz":
        tail = tail.replace(c, "c")
        
    return head + tail
    

def create_lookup_key(name):
    
    words = name.split(" ")
    result = ""
  
    for word in words:
        result += fingerprint(word)
    return result

print(create_lookup_key("Uwe Schmitt"))
print(create_lookup_key("A.K. Louis"))
print(create_lookup_key("Jenny Smith-Weber"))
uscmtt
aclc
jnnsmtb
from collections import defaultdict


def add_entry(name, phone_number, entries):
    key = create_lookup_key(name)
    entries[key].append((name, phone_number))
    

def lookup(name, entries):
    key = create_lookup_key(name)
    matches = entries.get(key, [])
    return matches


def exists(name, entries):
    for name_0, phone_number in lookup(name, entries):
        if name_0 == name:
            return True
    return False


def list_all(entries):
    for sub_entries in entries.values():
        for (name, phone_number) in sub_entries:
            yield name, phone_number


entries = defaultdict(list)
add_entry("Uwe Schmitt", "0041-076-123456", entries)
add_entry("A.K. Louis", "0049-0681-98765", entries)
add_entry("Janny Smith-Weber", "0033-0099-1234", entries)
print(entries)
print(lookup("uwe Schmatt", entries))
[('Uwe Schmitt', '0041-076-123456')]

We choose the json file format for storing our entries. This is a human readable text format which looks like dictionary with a few restrictions (e.g. keys must be strings):

import os
import json

# it is good style to write constant values in upper cases:
DEFAULT_FILE = "./phonebook.json"


def load_data(path=DEFAULT_FILE):
    if os.path.exists(path):
        with open(path, "r") as fh:
            return json.load(fh)
    else:
        return defaultdict(list)
    
    
def store_data(phone_book_entries, path=DEFAULT_FILE):
    with open(path, "w") as fh:
        json.dump(phone_book_entries, fh, indent=4)


d = load_data("test.json")

add_entry("Uwe Schmitt", "0041-076-123456", d)
add_entry("A.K. Louis", "0049-0681-98765", d)
add_entry("Janny Smith-Weber", "0033-0099-1234", d)

store_data(d)
print(open(DEFAULT_FILE, "r").read())
{
    "uscmtt": [
        [
            "Uwe Schmitt",
            "0041-076-123456"
        ]
    ],
    "aclc": [
        [
            "A.K. Louis",
            "0049-0681-98765"
        ]
    ],
    "jnnsmtb": [
        [
            "Janny Smith-Weber",
            "0033-0099-1234"
        ]
    ]
}
def submenu_add_entry(phone_book_entries):

    name = ask("name [. to abort]        : ")
    if name == ".":
        print("aborted")
        return
    phone_number = ask("phone_number [. to abort]: ")
    if name == ".":
        print("aborted")
        return
    if exists(name, phone_book_entries):
        print("entry for name {} exists: {}".format(name, phone_book_entries[name]))
        overwrite = ask("do you want to ovewrite this [y/n] ?", ["y", "n"])
        if overwrite == "n":
            return
    add_entry(name, phone_number, phone_book_entries)
 

    
def submenu_list_all(phone_book_entries):

    names = sorted(phone_book_entries.keys())
    if not names:
        print("phone book is still empty")
    else:
        print("entries:")
        for name, number in list_all(phone_book_entries):
            print("  {:20s}: {}".format(name, number))
        
submenu_list_all(entries)
entries:
  Uwe Schmitt         : 0041-076-123456
  A.K. Louis          : 0049-0681-98765
  Janny Smith-Weber   : 0033-0099-1234
def submenu_lookup_entry(phone_book_entries):

    name = ask("name: ")
    count = 0
    for name, number in lookup(name, phone_book_entries):
        print("{name} has phone number {number}".format(name=name, 
                                                        number=number))
        count += 1
        
    if count == 0:
        print("no entry for {}".format(name))

We can avoid if, elif, else for the chosen action by using a dictionary which maps the menu point number to the according function. This only works here if the functions for the sub menus have the same signature (same arguments):

def main():
    
    routines = {1: submenu_add_entry,
                2: submenu_lookup_entry,
                3: submenu_list_all}
    
    phone_book_entries = load_data()
    
    while True:
        todo = main_menu(phone_book_entries)
        print()
        if todo == 0:
            store_data(phone_book_entries)
            print("good bye")
            return
        
        # lookup and call function for chosen menu point 
        routines[todo](phone_book_entries)
        
main()   
PHONEBOOK v1.0
==============

Already 3 entries in the phone book

Please choose:

    1: add a new entry
    2: lookup a number
    3: show all entries
    0: exit

what to do ? 3

entries:
  Uwe Schmitt         : 0041-076-123456
  Uwe Schmatt         : 0001111
  A.K. Louis          : 0049-0681-98765
  Janny Smith-Weber   : 0033-0099-1234

PHONEBOOK v1.0
==============

Already 3 entries in the phone book

Please choose:

    1: add a new entry
    2: lookup a number
    3: show all entries
    0: exit

what to do ? 2

name: Uve Schmatt
Uwe Schmitt has phone number 0041-076-123456
Uwe Schmatt has phone number 0001111

PHONEBOOK v1.0
==============

Already 3 entries in the phone book

Please choose:

    1: add a new entry
    2: lookup a number
    3: show all entries
    0: exit

what to do ? 0

good bye