Несколько циклов 'for' в генераторе словарей

В эта документация Python в качестве примера выражения генератора используется следующее:

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',...}

Я не понимаю, как второй цикл for, for fn in (int, str), превращает значение int в строку и добавляет дополнительную запись в словарь.

Я нашел этот вопрос о переполнении Stack  , но я все еще не мог понять, как работает второй цикл for в этом случае.

Ответы

Ответ 1

В вашем примере вы строите словарь, используя последовательность (здесь предоставленную выражением генератора) кортежей, выраженную следующим литералом

(fn(i+1), code)

В этом кортеже буквально все термины (кроме 1:) предоставляются двумя циклами; внешний цикл

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

предоставляет вам i, целочисленное значение и code, длинную строку длиной 1 символ - эти значения фиксируются при выполнении внутреннего цикла; внутренний цикл предоставляет вам значение fn

… for fn in (int, str) …

который может принимать 2 значения, либо fn=int, либо fn=str.

Когда построена первая кортеж, i=0, code='T' и fn=int

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

когда построен второй кортеж, i и code (предоставленные внешним циклом) не изменились, но fn=str, поэтому новый кортеж, переданный конструктору словаря, равен

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

В этот момент внутренний цикл достиг своего конца... внешний цикл обновляет значения его переменных, i=1 и code=R, внутренний цикл reset, следовательно fn=int и создается новый кортеж

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

и т.д.

Ответ 2

Это может помочь "развернуть" циклы в выражении генератора и записать их как независимые циклы for. Для этого вы берете все операторы for (variable) in (iterable) и помещаете их в отдельные строки в том же порядке, но перемещаете вещь из фронта в тело самого внутреннего цикла. Как обычно,

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

становится

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

за исключением того, что когда вы выполняете выражение генератора, все thing автоматически переходят в список или словарь или что-то еще. В вашем случае

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

становится

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

за исключением того, что все кортежи будут преобразованы в dict.

Как объясняют другие ответы, вы можете отслеживать выполнение этих двух циклов for. Во-первых, внешний цикл устанавливает i в 0 и code в 'F', и внутри него внутренний цикл устанавливает fn в int, а затем в str, поэтому вы получаете

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

после чего он переходит к следующим i и code.

Ответ 3

Как вы можете видеть, fn(i+1) будет вызываться два раза. Сначала int(i+1), второй str(i+1)

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

Вывод:

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

Упрощенный цикл для лучшей читаемости:

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

Вывод:

0 F <type 'int'>
0 F <type 'str'>
1 G <type 'int'>
1 G <type 'str'>
2 H <type 'int'>
2 H <type 'str'>

Ответ 4

Причина в том, что из этого (fn(i+1), code)) генератор дает кортеж с первым элементом как либо int, либо string, а второе значение в виде буквы из 'FGHJKMNQUVXZ'

Вот еще один пример этого без второго цикла

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

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

Все, что происходит с for fn in (int, str), состоит в том, что fn может быть либо встроенной функцией int, либо str для преобразования значения i.

int(i)
str(i)

Ответ 5

Этот код использует это в именах классов Python, он также может использоваться для вызова функции-конструктора, которая возвращает экземпляр этого класса. Например:

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

a = A(10)

Обратите внимание, что A является как именем класса, так и может использоваться как Python callable. Приятная вещь в том, что int и str также могут использоваться одинаково!

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

Таким образом, второй цикл использует str и int как функции, которые возвращают либо строковое, либо целочисленное представление индекса в первом цикле for. Вы также можете представить, написав две функции:

def as_int(value):
    return int(value)

def as_str(value):
    return str(value)

И затем записывая цикл for следующим образом:

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`

Как вариант с длинной версией примера.