问题
I have a program which checks an outlook inbox for attachments from specific senders in a dictionary. Python - Outlook search emails within specific range only
That program works fine, and now I'm trying to expand on it by giving the user the ability to choose a specific sender from the dictionary, so as to only search their emails. This code is within the previous program.
The dictionary I have is senderDict = {sender@address.com : Sender Name}
To turn it into a menu of values to choose from I've used enumerate:
listSender = list(senderDict.values())
for count, item in enumerate(listSender,1):
print(count, item)
print("There are", len(listSender), "items.")
while True:
senderNum = int(input("Choose a sender (zero for any): "))
try:
if senderNum <= len(listSender) and senderNum >= 0:
#senderNum = int(input("Choose a sender (zero for any): "))
print("you chose", listSender[senderNum])
break
elif senderNum == 0:
print("Searching for any sender")
break
elif senderNum < 0:
print("Cannot choose a negative number.")
continue
except ValueError: #Not int
print("You did not enter a valid number (ValueError)")
input()
except IndexError: #outside range
print("You did not enter a valid number (IndexError)")
input()
The issue is that choosing zero will choose the zero index, instead of 'search any'. How do I make the list index match the enumerate values?
Also, entering a non-number or blank input crashes the program, so I'm not certain what exception covers that, I thought it fell under ValueError.
I'm not really sure if I even need to turn the dictionary into a list, but I had read that it was necessary for enumerate to work so I just went with it.
回答1:
it seems you have a little bug in your code!
if senderNum <= len(listSender) and senderNum >= 0:
The above line includes 0. 0 >= 0 == True
therefore
elif senderNum == 0:
will never be reached since it is already reached in the previous if statement. I believe what you wanted to do is
if senderNum <= len(listSender) and senderNum > 0:
This way the above line will be False when the chosen number is 0 and the elif line will be reached by your code. If you need it to match an index in your list, since you set your enumerate to start at 1 you can manually subtract 1 from the user's chosen value count - 1
.
In order to catch incorrect values you could add an else statement:
else:
print('Incorrect value')
continue
回答2:
The issue is that choosing zero will choose the zero index, instead of 'search any'. How do I make the list index match the enumerate values?
First fix the order of your tests, then substract 1 from the user's input.
Also, entering a non-number or blank input crashes the program, so I'm not certain what exception covers that, I thought it fell under ValueError.
int(something)
can raise either a TypeError
(if type(something)
isn't something that can be interpreted as numeric value) or ValueError
if the type is ok but not the value.
input()
(raw_input()
in py2)always returns a string so you cannot have a
TypeErroreher and
ValueError` is indeed what you want to catch - but you have to put your try/except block at the right place. Currently you have:
senderNum = int(input("Choose a sender (zero for any): "))
try:
# use senderNum here
except ValueError:
# ...
but the ValueError
is raised by int(...)
so it will indeed not be caught. You want:
try:
senderNum = int(input("Choose a sender (zero for any): "))
except ValueError:
print("not a valid value")
continue
# ok, now `senderNum` is an integer
Also since you test that senderNum
is within boundaries, you should not have any IndexError
. Well, actually you wouldn't if the test was correct - you should test that senderNum
is strictly smaller than len(listSender)
- remember that lists are zero-based, so the last index of a list is len(lst) - 1
:
>>> lst = list("abc")
>>> len(lst)
3
>>> 3 <= len(lst)
True
>>> lst[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> 3 < len(lst)
False
>>>
I'm not really sure if I even need to turn the dictionary into a list, but I had read that it was necessary for enumerate to work so I just went with it.
enumerate()
works on any iterable. A dict
is an iterable so you could just pass senderDict
to enumerate
directly - but then you'd have the keys, not names. Or you could just pass senderDict.values()
directly, but then you're going to have another issue, which is how to get the email back from the name.
The proper solution here is to turn your dict into a list of (key, value)
tuples - which is as simple as list(senderDict.items())
, so you can get both the name and email from the choosen list index.
Corrected code:
senders = list(senderDict.items())
# this won't change during execution so let's
# stop recomputing it each time
nb_senders = len(senders)
for index, (email, name) in enumerate(senders, 1):
print("{}: {} ({})".format(index, name, email)
print("There are {} items.".format(nb_senders))
while True:
# get the raw input
senderNum = input("Choose a sender (zero for any): ")
# now validate it
try:
senderNum = int(senderNum)
if senderNum < 0:
raise ValueError("should not be negative")
if senderNum > nb_senders:
raise ValueError("should be lesser than {}".format(nb_senders))
except ValueError as e:
print("'{}' is not valid choice: {}".format(senderNum, e))
continue
# ok, done
if senderNum == 0:
print("Searching for any sender")
break
print("you chose {} ({})".format(*listSender[senderNum]))
break
回答3:
Your question is how to list index match the enumerate values. That is quite easy you only have to subtract 1 from the input your user provides you. The problem is in your if clause senderNum >= 0
that will be true if your user inputs 0 so the elif senderNum == 0
will never be true
For your crash when you enter a non-number that happens because you have the try
after the conversion of the entry into an int. So you have to put the try befor the int(input("Choose a sender (zero for any): "))
My suggestion is rather to choose negative number for sending to any.
So my suggestion is the following:
listSender = list(senderDict.values())
for count, item in enumerate(listSender):
print(count, item)
print("There are", len(listSender), "items.")
while True:
try: # have to be before the int(input(...)) to catch the exception
senderNum = int(input("Choose a sender (-1 for any): "))
if senderNum < len(listSender) and senderNum >= 0: # len(listSender) is not allowed because the index starts at 0 and the last entry is len(listSender) -1
print("you chose", listSender[senderNum])
break
elif senderNum < 0:
print("Search for any sender")
break
except ValueError: #Not int
print("You did not enter a valid number (ValueError)")
input()
except IndexError: #outside range
print("You did not enter a valid number (IndexError)")
input()
if you really want to use 0 as for send any I then you have to do the following:
listSender = list(senderDict.values())
for count, item in enumerate(listSender,1):
print(count, item)
print("There are", len(listSender), "items.")
while True:
try:
senderNum = int(input("Choose a sender (zero for any): "))
if senderNum <= len(listSender) and senderNum > 0: # if senderNum is zero then send for any so senderNum >= 0 has to be changed to senderNum > 0
print("you chose", listSender[senderNum-1]) # because index starts with 0 you have to subtract 1 from the input
break
elif senderNum == 0:
print("Searching for any sender")
break
elif senderNum < 0:
print("Cannot choose a negative number.")
continue
except ValueError: #Not int
print("You did not enter a valid number (ValueError)")
input()
except IndexError: #outside range
print("You did not enter a valid number (IndexError)")
input()
To address your last question about enumerate and dictionary/list. A dictionary is not ordered so that is why you can't enumerate through it with an index. So it is necessary to do a conversion into a list. The other way to use it without casting it into a list is to use the dictionary keys as input instead of index numbers.
回答4:
Thanks for all the helpful responses!
Instead of trying to change the value of the input, I decided to insert a list option for 'Search Any' after converting the dictionary to a list, then let enumerate start from zero. This way I don't have to do any addition or subtraction and the index values still match the input.
listSender = list(senderDict.values())
listSender.insert(0, "Search Any")
for count, item in enumerate(listSender):
print(count, item)
来源:https://stackoverflow.com/questions/59263216/reference-list-index-to-enumerate-value