简体   繁体   中英

Reference list index to enumerate value

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.

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

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.

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 TypeError eher 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

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM