Search code examples
pythonloopsdictionarytuplesenumerate

Why does looping through dict-value tuples using `enumerate()` throw an error?


This is a result of my answer to another question.

  1. Why do I get a TypeError as described below?
  2. What's the deal with enumerate?

I thought that

for k, v in enumerate(iterable)`

was equivalent to:

for k in iterable:
    v = iterable[k]

Apparently it's not. Or at least, it's not equivalent in all circumstances.

Python docs say:

... seasons = ['Spring', 'Summer', 'Fall', 'Winter']

... list(enumerate(seasons))

[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]

... list(enumerate(seasons, start=1))

[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

I would have thought then, that enumerate({1:(1,0)} would return (1, (1,0)).

It doesn't though, it returns an enumerate object:

=> print enumerate(['list'])
<enumerate object at 0x204bb80>

What's really weird is the type of error, and when it's thrown (why do the first two loops go?) when you do things wrong.

Question 1: why does this error occur?

dicModul = {0:(0,1),1:(1,1), 2:(2,3,4,5,6,1,7,2), 
              3:(8,1),4:(9,9), 5:(10,10,5,11,0,12,13), 6:(10,11,9,7)}

for val_outerloop, key_outerloop in enumerate(dicModul):
    for val_innerloop, key_innerloop in enumerate(dicModul):
        print "outerloop {}:{}".format(key_outerloop, val_outerloop)
        print "innerloop {}:{}".format(key_innerloop, val_innerloop)
        for x in val_innerloop:
            print "loops!"

Output of the above code:

outerloop 0:0
innerloop 0:0
Traceback (most recent call last):
  File "python", line 8, in <module>
TypeError: 'int' object is not iterable

I can't figure out what an enumerate object is (doesn't seem to be in the python docs), and in any case, based on those docs, what I'm trying to do shouldn't it doesn't seem throw an error, especially not the TypeError that it's throwing.

Background info is below.

I know (or thought I knew) the following:

Enumerate() allows you to get both the keys and values of items in your dictionary, or the iteration number and value if you are looping through a list.

With regard to your issue with iterating over single items. Tuples are weird, especially with single items. You need the trailing comma or it's not a tuple, it's an int. Honestly, that strikes me as a bug, not a feature, but optimistically, it's probably important for some computer-sciency reason that's way beyond my paygrade. Also, the big thing that distinguishes lists [] and tuples () is that tuples are immutable. You probably don't need to be using tuples in this case:

loopy = (1,)
for x in loopy:
    print x # this will _not_ raise an error

loopy = (1)
for x in loopy:
    print x # this _will_ raise an error

loopy = [1]
for x in loopy:
    print x # this will _not_ raise an error

loopy = 1
for x in loopy:
    print x # this _will_ raise an error

Here are two sets of code:

Codeset 1

dicModul = {0:(0,1),1:(1,1), 2:(2,3,4,5,6,1,7,2), 
          3:(8,1),4:(9,9), 5:(10,10,5,11,0,12,13), 6:(10,11,9,7)}

for val_outerloop, key_outerloop in enumerate(dicModul):
    for val_innerloop, key_innerloop in enumerate(dicModul):
        print "outerloop {}:{}".format(key_outerloop, val_outerloop)
        print "innerloop {}:{}".format(key_innerloop, val_innerloop)

which outputs:

outerloop 0:0
innerloop 0:0
outerloop 0:0
innerloop 1:1
... (you get the idea) ...
innerloop 3:3
outerloop 6:6
innerloop 4:4
outerloop 6:6
innerloop 5:5
outerloop 6:6
innerloop 6:6

Codeset 2

for key_outerloop in dicModul:
    for key_innerloop in dicModul:
        val_outerloop = dicModul[key_outerloop]
        val_innerloop = dicModul[key_innerloop]
        print "outerloop {}:{}".format(key_outerloop, val_outerloop)
        print "innerloop {}:{}".format(key_innerloop, val_innerloop)

which outputs (more what I would expect):

outerloop 0:(0, 1)
innerloop 0:(0, 1)
outerloop 0:(0, 1)
innerloop 1:(1, 1)
outerloop 0:(0, 1)
innerloop 2:(2, 3, 4, 5, 6, 1, 7, 2)
outerloop 0:(0, 1)
innerloop 3:(8, 1)
... (you get the idea) ...
outerloop 6:(10, 11, 9, 7)
innerloop 5:(10, 10, 5, 11, 0, 12, 13)
outerloop 6:(10, 11, 9, 7)
innerloop 6:(10, 11, 9, 7)

Solution

  • There are a few misunderstandings here.

    enumerate will give you a counting variable, not the key of the dictionary:

    l = ['a', 'b', 'c']
    for i, entry in enumerate(l):
        print(i, l)
    

    will result in

    0 a
    1 b
    2 c
    

    When you iterate over a dictionary, you are just iterating over the keys:

    d = {'a': (2, 3), 'b': (3, 4), 'c': (5, 5)}
    for key in d:
        print(key)
    

    will result in

    a
    b
    c
    

    If you want to iterate over both keys and values use d.items() (python3) or d.iteritems() (python2):

    d = {'a': (2, 3), 'b': (3, 4), 'c': (5, 5)}
    for key, val in d.items():
        print(key, val)
    

    will result in

    a (2, 3)
    b (3, 4) 
    c (5, 5)
    

    You can combine this like this:

    for i, (key, val) in enumerate(d.items()):