title: Exception handled by wrong function? (Python 3.x)
body: <p>I have made a program to check emails and grab attachments from specific senders in outlook. There is a main menu and a sub menu, the two menus are unique functions wrapped in separate try-except blocks to deal with invalid input. When I intentionally type the wrong input into the sub menu, it displays an exception from the main menu. Is this how exception handling should work, or is there a way to solve this?</p>
  
  <p>I was PREVIOUSLY using two <code>While True:</code> style loops for each menu. I thought that might mean the top menu loop was still running once I accessed the sub menu, causing it to use exception handling from the top menu loop with sub menu input. I changed it so that moving to other menus changes the current menu loop to False. Unfortunately the sub menu still displays exceptions from the top menu, while the top menu never displays exceptions from the sub menu (as far as I have encountered).</p>
  
  <pre><code>import win32com.client, os, datetime, time, sys
  
  def main():
     #Opens Microsoft Outlook as outlook
     outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
  
     ######These variables are in a try except clause because itcontents will throw an error when
     ###### a sender uses something like emojis in their subject line.
  
     try:
         itinbox = outlook.Folders("IT Department").Folders('Inbox')#Look at inbox of IT Dept folder
         itcontents = itinbox.Items #messages in the inbox        
         newMail = itcontents.GetLast() #GetLast is the most RECENT email.
         nextMail = itcontents.GetPrevious() #The next most recent email after .GetLast()
         mailSubj = newMail.Subject #The subject header
         totalEmails = itcontents.Count
         #print("Most recent mail:", newMail) #subject header of most recent email
         #print("sent from:", newMail.Sender)
  
     except UnicodeEncodeError: #unsupported characters
         print("Most recent message had unsupported characters.")
  
  
  
     #Shows the 'class' value of the most recent inbox item.
     for msg_class in range(1):
         print("Message class value of most recent inbox item is:", itcontents.GetLast().Class)
  
     emailUser = os.getlogin()
     print("Current user is", emailUser)
  
     todayDate = datetime.date.today()
  
     #############
     #A dictionary of sender addresses and associated sender names
     #Some addresses are sent on behalf of/bounced from another address
     #Use str(msg.SentOnBehalfOfName) in this case because the bounce address will change
  
     senderDict = {"sender name" : "sender email"} #redacted details, there are about 10 unique emails
  
     listSender = list(senderDict.values())
     listSender.insert(0, "Search Any")
  
  
     ############Top level menu
  
     def topMenu():
         topMenuItems = ['Collect Attachments', 'View Senders', 'Edit Senders', 'Quit']
         topMenuDisplay = True
         while topMenuDisplay == True:
             try:
                 for count, item in enumerate(topMenuItems,1):
                     print(count, item)
                 mainChoice = int(input('\nChoose an option: ')) #subtract one to match enumerate to index
                 mainChoice -= 1
                 print("Chose", topMenuItems[mainChoice], "\n")
  
                 ###Collect Attachments
                 if mainChoice == 0:
                     topMenuDisplay = False #Intended to stop the topMenu loop
                     mainChoice0() #Go to sub menu
  
                 elif mainChoice == 1:
                     print(senderDict, "\n")
                     input("Press any key")
                     continue
  
                 elif mainChoice == 2:
                     print("coming soon\n")
                     continue
  
                 elif mainChoice == 3:
                     input("Press any key to exit.")
                     sys.exit("Goodbye")
  
                 #avoid negative number index access, still displays a menu choice but prints error
                 elif mainChoice &lt; 0: 
                     print("Your choice is not listed.")
                     continue
  
                 #For any other input I haven't considered
                 else:
                     print("Your choice is not listed.")
                     continue
  
             except ValueError: #Not int
                 print("You did not enter a valid number at topMenu() (ValueError)")
                 continue
  
  
             except AttributeError:
                 print("You did not enter a valid number at topMenu() (AttributeError)")
                 continue
  
  
             except IndexError: #outside range
                 print("Selection was not in the list at topMenu() (IndexError)")
                 continue
  
  
  ############# Sub menu to collect email attachments from sender dictionary
  
     def mainChoice0(): 
         check = 0 #Keep count of checked messages
  
         mainChoiceDisplay0 = True
  
         #Make a folder on user desktop if it doesn't exist
         if not os.path.exists("C:\\Users\\"+emailUser+"\\Desktop\\Invoices from Outlook\\"):
             os.makedirs("C:\\Users\\"+emailUser+"\\Desktop\\Invoices from Outlook\\")
             print("Created folder 'Invoices from Outlook' on user desktop.")
  
  
         while mainChoiceDisplay0 == True:
             try:
                 ####Determine search range by date
                 startDate = input("Enter a date dd-mm-yyyy (blank to exit): ")
                 if startDate == "":
                     mainChoiceDisplay0 = False
                     topMenu()
  
                 #strptime will convert startDate from a string to a usable date
                 startDate = datetime.datetime.strptime(startDate, "%d-%m-%Y") #class and module are called datetime hence datetime.datetime.strptime
                 searchRange = int(input("Enter how many days previous to search through: "))
                 searchDate = startDate.date() - datetime.timedelta(days = searchRange)#Subtract number of days from user input date
                 print("Your search starts from", startDate, "and ends on", searchDate, "\n")
  
                 #display list of senders for mainChoice == 0
                 for count, item in enumerate(listSender,0): 
                     print(count, item)
                 print("There are", len(listSender), "options.\n")
  
  
  
                 #Choose sender from menu
                 senderNum = input("Choose a sender (blank to exit): ")
                 if senderNum == "":
                     mainChoiceDisplay0 = False
                     topMenu()
                 else:
                     senderNum = int(senderNum)
  
                 senderChoice = listSender[senderNum]
  
                 #Search all
                 if senderNum == 0:
                     print("Searching for any sender")
  
  
                     for msg in reversed(itcontents): #reversed() will go from most recent to oldest email based on date
                         if msg.Class == 43:
  
                             if ( (str(msg.SenderEmailAddress) or str(msg.Subject) or str(msg.Sender) or str(msg.SentOnBehalfOfName)) in senderDict and (msg.SentOn.date() &gt;= searchDate and msg.SentOn.date() &lt;= startDate.date())):
                                 check += 1
  
                                 print(check, "messages from", msg.SenderEmailAddress, "on", msg.SentOn.date())
                                                         ###############################
                                 #print("Looking at", msg, msg.SentOn.date()) #uncomment this to see details about every message
  
                                 #Check attachment file format string, invoices are usually PDFs.
                                 #x refers to the attachment. Not every message from a listed sender has an attachment, those messages still add to count check.
                                 for x in msg.Attachments:
                                     if str(".pdf").casefold() in str(x): #casfold() checks possible upper or lower case combinations e.g PdF or pDf
                                         x.SaveAsFile("C:\\Users\\"+emailUser+"\\Desktop\\Invoices from Outlook\\" + str(msg.SentOn.date()) + str(msg.SenderEmailAddress) + x.FileName)
                                         print("Saved attachment", x, "from", str(msg.Sender()), "on", str(msg.SentOn.date()))
                                         #break
                                         #Stop searching inbox earlier than searchDate
                                         if msg.SentOn.date() &lt; searchDate: 
                                             break
  
  
                 elif senderNum &lt;= len(listSender) and senderNum &gt; 0:
                     print("you chose", senderChoice)
  
                     #msg refers to an inbox object, msg.Class == 43 is a mail item object
                     for msg in reversed(itcontents): #reversed() will go from most recent to oldest email based on date
                         if msg.Class == 43: #Don't call msg.Class as a function with (), just need integer value
  
                     #senderNum != 0 for all other valid options.
                     #https://www.programiz.com/python-programming/methods/built-in/any
                     #list comprehension [x for x in [items] if y in x] https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Comprehensions.html
                             if ( (str(msg.SenderEmailAddress) or str(msg.Subject) or str(msg.Sender) or str(msg.SentOnBehalfOfName)) in senderDict
                             and
                             (msg.SentOn.date() &gt;= searchDate and msg.SentOn.date() &lt;= startDate.date())
                             and
                             any([field for field in [str(msg.SenderEmailAddress), str(msg.Subject), str(msg.Sender), str(msg.SentOnBehalfOfName)] if str(senderChoice) in field])
                             ):
                                 check += 1
                                 print(check, "messages from", msg.SenderEmailAddress, "on", msg.SentOn.date()) #keep count of checked messages
  
                                 #Check attachment file format, invoices are usually PDFs
                                 #x refers to the attachment. Not every message from a listed sender has an attachment, those messages still add to count check.
                                 for x in msg.Attachments:
                                     if str(".pdf").casefold() in str(x): #casfold() cheks upper or lower case format
                                             x.SaveAsFile("C:\\Users\\"+emailUser+"\\Desktop\\Invoices from Outlook\\" + str(msg.SentOn.date()) + str(msg.SenderEmailAddress) + x.FileName)
                                             print("Saved attachment", x, "from", str(msg.Sender()), "on", str(msg.SentOn.date()))
  
                                 #Stop searching inbox earlier than searchDate
                                 if msg.SentOn.date() &lt; searchDate: 
                                     break
  
  
  
             except UnicodeEncodeError: #unsupported characters
                 print("Subject line could not be parsed.")
                 #continue
  
             except OverflowError: #caused by invalid or very old date e.g 01-01-0000
                 print("Date calculation caused an overflow error, possibly because you entered a very old date e.g 01-01-0000.")
                 #break
  
             except AttributeError: #The email was the wrong class for the attribute msg.Sender or you tried to use an attribute that doesn't work for that method
                 print("Attribute error (not class 43 mail) for item - ", msg, msg.SentOn.date())
                 continue
  
     topMenu()
  main() 
  </code></pre>
  
  <p>Output looks like:</p>
  
  <pre><code>Message class value of most recent inbox item is: 43
  Current user is camerona
  1 Collect Attachments
  2 View Senders
  3 Edit Senders
  4 Quit
  
  Choose an option: 1
  Chose Collect Attachments
  
  ####Sub menu starts here
  
  Enter a date dd-mm-yyyy (blank to exit): 04-02-2020
  Enter how many days previous to search through: 1
  Your search starts from 2020-02-04 00:00:00 and ends on 2020-02-03
  
  0 Search Any
  1 redacted1
  2 redacted2
  3 redacted3
  4 redacted4
  5 redacted5
  6 redacted6
  7 redacted7
  8 redacted8
  There are 9 options.
  
  Choose a sender (blank to exit):
  a
  You did not enter a valid number at topMenu() (ValueError)
  </code></pre>
  
  <p>So as you can see, when I type <code>'a'</code> in the sub menu to cause an exception, it gets handled by topMenu() instead.</p>