Несколько циклов '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`
Как вариант с длинной версией примера.