Problem
I’m trying my hand at an address book and the goal is for it to have a bit more of error prevention. For instance, for the key “phone” to have 10 digits, and not allow alpha characters at section 4. I believe I have formatted this properly to the best of my ability. I understand this is very elementary as I’m new to any sort of code.
My goal is to call this properly. I originally was using defs for name, phone, email but this way seemed more concise. I’m unsure how my written code can formatted to apply to the ‘phone’ key in the dictionary.
I certainly would like to hear opinions of how I could make this into a class with the name, phone, and email being def
s as that was my original goal.
def main():
# Creates an empty list of contacts
contactlist = collections.OrderedDict()
loops = True
# While loop used for options, it loops for user input until sequence is over
while loops == True:
# Prints option list for the user
print ('Greetings user! Lets make an addressbook!', 'n', 'n', 'Would you like to: ', 'n', '1.) Add a New Contact', 'n', '2.) List All Contacts', 'n', '3.) Search Contacts', 'n','4.) Edit A Contact', 'n', '5.) Delete A Contact', 'n', '6.) Quit Program')
# Asks for users input from 1-6
userInput = input("Select an option: ")
# 1 : Add a new contact to list, also start of program if no file is made
if userInput == "1":
contactname = input( "Enter name: ")
contactlist[contactname] = {'name': contactname, 'phone': input("Enter phone number: "), 'email': input("Enter email: ").lower()}
json.dump(contactlist, open('contacts.txt','w'))
# Error Checking
#phone number
'''[k]['phone']
while True:
try:
if len(phone) != 10:
print('Not 10 digits')
else:
phone = (phone[:3]+'-'+phone[3:6]+'-'+phone[6:9])
return phone
except:
print("Not all numbers inputed are digits")
'''
print ("Contact Added!")
# 2 : list of contacts in addressbook
elif userInput == "2":
print ('n', "Listing Contacts...")
try:
contactlist = json.load(open('contacts.txt','r'))
name_keys = list(contactlist.keys())
except:
contacts = {}
print ("%-30s %-30s %-30s" % ('NAME','PHONE','EMAIL'))
#better formatting than using tab spaces and keeps items in a predetermined space apart from eachother
for k in name_keys:
print ("%-30s %-30s %-30s" % (contactlist[k]['name'], contactlist[k]['phone'], contactlist[k]['email']))
#same idea as fotmatting above for each of the dict values
# 3 : search through contacts!
elif userInput == "3":
print ('n', "Searching Contacts...")
search = input("Please enter name (case sensitive): ")
try:
contactlist = json.load(open('contacts.txt','r'))
except:
contactlist = []
try:
print ("%-30s %-30s %-30s" % (contactlist[search]['name'], contactlist[search]['phone'], contactlist[search]['email']))
except KeyError: #error reporting- whenever a dict() object is requested & key is not in the dict.
print ("Not Found")
##########################################################################
#4 : Edit a contact!
elif userInput == "4":
print ('n', "Editing Contact...")
search_edit = input("Please enter name: ")
try:
contactlist.pop(contactname)
json.dump(contactlist, open('contacts.txt','w'))
except KeyError: #error reporting- whenever a dict() object is requested & key is not in the dict.
print ('n', "Contact Not Found")
# 5 : Delete contact
elif userInput == "5":
print ("Deleting Contact...")
contactname = input("Enter Contact Name: ")
contacts = json.loads(open('contacts.txt').read())
try:
contacts.pop(contactname)
json.dump(contacts, open('contacts.txt','w'))
except KeyError: #error reporting- whenever a dict() object is requested & key is not in the dict.
print ('n', "Contact Not Found")
# 6 : end program
elif userInput == "6":
print ('n', "Ending Contact Book.")
print('Have a nice day!')
loop = False
else:
print ("Invalid Input! Try again.")
main()
Solution
loops = True
while loops == True:
…
if …:
loops = False
You can simplify this logic using break
:
while True:
…
if …:
break
contactlist = collections.OrderedDict()
Well, is it a list
or a dict
? The naming is at least misleading, try to use contacts
or something alike. However, there are some other places with contactlist = json.load(…)
, contactlist = {}
or even contactlist = []
; which make it either a regular dict or a list. All of that is very confusing.
You should simplify the logic by loading your contacts at the beginning of the function and use that all along:
try:
with open('contact.txt') as f:
contacts = json.loads(f)
except (FileNotFoundError, json.decoder.JSONDecodeError):
# File not found or empty, use a default container
contacts = {}
You should note the use of with
in this snippet: each time you open
a file, you should close
it afterwards to release associated resources. Using with
ensures the file will be closed whatever happens (success or exception raised).
This will simplify #2, #3 and #5 as the contacts are already loaded.
print ('Greetings user! …')
You can make this kind of lines much more readable by using multi-line strings:
def main():
try:
with open('contact.txt') as f:
contacts = json.loads(f)
except (FileNotFoundError, json.decoder.JSONDecodeError):
# File not found or empty, use a default container
contacts = {}
while True:
print('''Greetings user! Let's make an addressbook!
Would you like to:
1.) Add a New Contact
2.) List All Contacts
3.) Search Contacts
4.) Edit A Contact
5.) Delete A Contact
6.) Quit Program''')
…
Speaking of which: multi-line strings are not comments. They are just random noise in the code. You wouldn’t put a random integer in your code, so don’t do it with random strings.
You can simplify some of your logic by using helper functions. Printing informations on a contact should be done in #2 and #4; asking new informations about a contact should be done in #1 and #4. So it’s worth using functions to remove redundancy:
def print_contact(contact_info):
print("{0[name]:<30} {0[phone]:<30} {0[email]:<30}".format(contact_info))
It uses the new format syntax which is the prefered way of doing in Python 3.
def create_contact(name=None):
if name is None: # Name not provided, so we need to ask for it
name = input("Enter name: ")
phone = input("Enter phone number: ")
email = input("Enter email: ").lower()
# Do whatever validation you like on `phone` or `email`
return {'name': name, 'phone': phone, 'email': email}
You could also extend this function by having a “validation” phase and raising an exception in case of error, you’ll then need to account for that in your calling code:
class NotAPhoneNumberException(ValueError):
pass
def print_contact(contact_info):
print("{0[name]:<30} {0[phone]:<30} {0[email]:<30}".format(contact_info))
def create_contact(name=None):
if name is None: # Name not provided, so we need to ask for it
name = input("Enter name: ")
phone = input("Enter phone number: ")
email = input("Enter email: ").lower()
# Do whatever validation you like on `phone` or `email`
# and `raise` an exception if need be
return {'name': name, 'phone': phone, 'email': email}
def main():
try:
with open('contact.txt') as f:
contacts = json.loads(f)
except (FileNotFoundError, json.decoder.JSONDecodeError):
# File not found or empty, use a default container
contacts = {}
while True:
print('''Greetings user! Let's make an addressbook!
Would you like to:
1.) Add a New Contact
2.) List All Contacts
3.) Search Contacts
4.) Edit A Contact
5.) Delete A Contact
6.) Quit Program''')
choice = input("Select an option: ")
if choice == "1":
try:
new_contact = create_contact()
except NotAPhoneNumberException:
print("The phone number entered is invalid, creation aborted!")
else:
contacts[new_contact['name']] = new_contact
with open('contact.txt', 'w') as f:
json.dump(contacts, f)
elif choice == "2":
…
main()
When having code at top-level like that, it is good practice to use the if __name__ == '__main__':
clause.
You should also give your function a better name which describe what the function does. Lastly, it would be much more usefull if the function take the filename ('contact.txt'
) as a parameter.
All in all, you code can become:
import json
class NotAPhoneNumberException(ValueError):
pass
def save_contacts(contacts, filename):
with open(filename, 'w') as f:
json.dump(contacts, f)
def print_contact(contact_info):
print("{0[name]:<30} {0[phone]:^30} {0[email]:>30}".format(contact_info))
def create_contact(name=None):
if name is None: # Name not provided, so we need to ask for it
name = input("Enter name: ")
phone = input("Enter phone number: ")
email = input("Enter email: ").lower()
if len(phone) != 10: # Very simple validation
raise NotAPhoneNumberException
return {'name': name, 'phone': phone, 'email': email}
def address_book(filename):
try:
with open(filename) as f:
contacts = json.loads(f)
except (FileNotFoundError, json.decoder.JSONDecodeError):
# File not found or empty, use a default container
contacts = {}
while True:
print('''Greetings user! Let's make an addressbook!
Would you like to:
1.) Add a New Contact
2.) List All Contacts
3.) Search Contacts
4.) Edit A Contact
5.) Delete A Contact
6.) Quit Program''')
choice = input("Select an option: ")
if choice == "1":
try:
new_contact = create_contact()
except NotAPhoneNumberException:
print("The phone number entered is invalid, creation aborted!")
else:
contacts[new_contact['name']] = new_contact
save_contacts(contacts, filename)
elif choice == "2":
print_contact({'name': 'NAME', 'phone': 'PHONE', 'email': 'EMAIL'})
for contact in contacts.values():
print_contact(contact)
elif choice == "3":
search = input("Please enter name (case sensitive): ")
try:
print_contact(contacts[search])
except KeyError:
print("Contact not found")
elif choice == "4":
search = input("Please enter name (case sensitive): ")
try:
print_contact(contacts[search])
except KeyError:
print("Contact not found")
else:
try:
contacts[search] = create_contact(search)
except NotAPhoneNumberException:
print("Invalid phone number. Contact was not edited!")
else:
save_contacts(contacts, filename)
elif choice == "5":
search = input("Please enter name (case sensitive): ")
try:
contacts.pop(search)
except KeyError:
print("Contact not found")
else:
save_contacts(contacts, filename)
elif choice == "6":
print("Ending Contact Book.nHave a nice day!")
break
else:
print("Invalid Input! Try again.")
if __name__ == "__main__":
address_book('contact.txt')
You could simplify further by using a dictionary mapping to functions instead of all those elif
but let’s call it a day.
I would try using others code in this endeavor like the phonenumbers module for verification and parsing/printing. There are example code out there which can help your formatting. You want to break up your code into manageable chunks so you could put these into libraries if this gets to be large. e.g. https://retrosnob.wordpress.com/2014/10/14/command-line-contact-management-application-in-python/