13

In this Python documentation the following is used as an example of a generator expression:

dict((fn(i+1), code)
    for i, code in enumerate('FGHJKMNQUVXZ')
    for fn in (int, str))

>> {1: 'F', '1': 'F', 2: 'G', '2': 'G', 3: 'H', '3': 'H', 4: 'J',...}

I don't understand how the second for loop, for fn in (int, str), turns the int value into a string and adds an additional entry to the dictionary.

I have found this Stack Overflow question, but I still wasn't able to intuit how the second for loop works in this case.

Community
  • 1
  • 1
Sean
  • 963
  • 1
  • 10
  • 28

5 Answers5

8

It may help to "unroll" the loops in the generator expression, and write them as independent for loops. To do so, you take all the for (variable) in (iterable) statements and put them on separate lines, in the same order, but move the thing from the front to the body of the innermost for loop. Like this, in general:

thing for a in a_list for b in b_list for c in c_list

becomes

for a in a_list:
    for b in b_list:
        for c in c_list:
            thing

except that when you do the generator expression, all the things automatically go into the list or dictionary or whatever. In your case,

dict((fn(i+1), code)
    for i, code in enumerate('FGHJKMNQUVXZ')
    for fn in (int, str))

becomes

for i, code in enumerate('FGHJKMNQUVXZ'):
    for fn in (int, str):
        (fn(i+1), code)

except that all the tuples will be converted into a dict.

As the other answers explain, you can trace the execution of these two for loops. First, the outer loop sets i to 0 and code to 'F', and within that, the inner loop sets fn to int and then to str, so you get

int(0+1, 'F')
str(0+1, 'F')

after which it goes on to the next i and code.

David Z
  • 128,184
  • 27
  • 255
  • 279
  • I like this way of explaining by code transformation! If it were me, I would maybe only use two levels in general intro to stay close to the question and also avoid `a_list` in favor of `a_seq` or `a_iterable` (list is so concrete) - but it is not me ;-) – Dilettant Dec 27 '16 at 13:06
  • @Dilettant I wanted to use a different number of levels to show how the technique generalizes. You're right that something like `a_iter` would probably be a better name, but I figured it's not too important because learners at this level are not too concerned with the differences between lists and iterables. – David Z Dec 27 '16 at 19:51
  • Thanks for the thoughtful feedback and I share the intent as generalizing is exactly what it is - I am often astonished what exactly hits the eye of a learner and what is derived from it - esp. when learning myself :-) – Dilettant Dec 27 '16 at 19:54
4

As you can see fn(i+1) will be called two times. First int(i+1), second str(i+1)

for a in ((fn(i+1), code)
                    for i, code in enumerate('FGH')
                    for fn in (int, str)):
    print a

Output:

(1, 'F')
('1', 'F')
(2, 'G')
('2', 'G')
(3, 'H')
('3', 'H')

Simplified loop for better readability:

for i, code in enumerate('FGH'):
    for fn in (int, str):
        print i, code, fn

Output:

0 F <type 'int'>
0 F <type 'str'>
1 G <type 'int'>
1 G <type 'str'>
2 H <type 'int'>
2 H <type 'str'>
Mohammad Yusuf
  • 16,554
  • 10
  • 50
  • 78
3

The reason is because of this (fn(i+1), code)) the generator yields a tuple with the first item as either an int or string and the second value as a letter from 'FGHJKMNQUVXZ'

Here is another example of it without the second for loop

def gen_func(text):
    for i, code in enumerate(text):
        yield i, code
        yield str(i), code

print(dict(gen_func('FGHJKMNQUVXZ')))

All the happens with for fn in (int, str) is that fn can then be either the built-in int or str function to convert the value of i.

int(i)
str(i)
Steven Summers
  • 5,079
  • 2
  • 20
  • 31
2

The code is making use of that in Python class names it can also be used to call the constructor function that returns an instance of that class. For example:

class A(object):
    def __init__(value):
        self.value = value

a = A(10)

Note that A is both a class name and can also be used as a Python callable. The neat thing here is that int and str can also be used in the same way!

z = 10
value = int(z) # Returns number 10
print isinstance(value, int) # Prints True
print isinstance(value, str) # Prints False
value = str(z) # returns '10'
print isinstance(value, int) # Prints False
print isinstance(value, str) # Prints True

So the second loop is using str and int as functions that return either the string or integer representation of the index in the first for loop. You can also imagine having written two functions like this:

def as_int(value):
    return int(value)

def as_str(value):
    return str(value)

And then writing the for loop like this:

dict((fn(i+1), code)
for i, code in enumerate('FGHJKMNQUVXZ')
for fn in (as_int, as_str))
 # This second loop loops over the 2-element tuple 
 # whose contents are the two functions `as_int` and `as_str`

As a long-form version of the example.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
2ps
  • 15,099
  • 2
  • 27
  • 47
1

In your example you are constructing a dictionary using a sequence (here provided by a generator expression) of tuples expressed by the following literal

(fn(i+1), code)

In this tuple literal all the terms (except 1:) are provided by the two loops; the outer loop

… for i, code in enumerate('TROIDSNB') …

provides you i, an integer value and code, a 1 character long string — these values are fixed when you execute the inner loop;
the inner loop provides you the value of fn

… for fn in (int, str) …

that can assume 2 values, either fn=int or fn=str.

When the first tuple is constructed, i=0, code='T' and fn=int

(int(0+1), 'T')

when the second tuple is constructed, i and code (provided by the outer loop) have not changed but fn=str, so the new tuple, passed to the dictionary constructor, is

(str(0+1), 'T')

At this point the inner loop has reached its end… the outer loop updates the values of its variables, i=1 and code=R, the inner loop is reset, hence fn=int and a new tuple is generated

(int(1+1), 'R')

etc etc

gboffi
  • 22,939
  • 8
  • 54
  • 85