Почему в циклах for допускаются произвольные целевые выражения?
я случайно написал такой код:
foo = [42]
k = {'c': 'd'}
for k['z'] in foo: # Huh??
print k
но, к моему удивлению, это не синтаксическая ошибка. Вместо этого он печатает {'c': 'd', 'z': 42}
.
мой Угадай это то, что код переводится буквально на что-то вроде:
i = iter(foo)
while True:
try:
k['z'] = i.next() # literally translated to assignment; modifies k!
print k
except StopIteration:
break
но... почему это разрешено языком? Я ожидал бы, что только одиночные идентификаторы и кортежи идентификаторов должны быть разрешены в целевое выражение для-stmt. Быть там любая ситуация, в которой это действительно полезно, а не просто странный gotcha?
4 ответа:
The
for
цикл следует стандартным правилам назначения, поэтому то, что работает на LHS назначения vanilla, должно работать сfor
:каждый элемент в свою очередь присваивается целевому списку с помощью стандарта правила для заданий
The
for
построить просто вызывает базовый механизм для назначения цели, которая в случае вашего примера кодаSTORE_SUBSCR
:>>> foo = [42] >>> k = {'c': 'd'} >>> dis.dis('for k["e"] in foo: pass') 1 0 SETUP_LOOP 16 (to 18) 2 LOAD_NAME 0 (foo) 4 GET_ITER >> 6 FOR_ITER 8 (to 16) 8 LOAD_NAME 1 (k) 10 LOAD_CONST 0 ('e') 12 STORE_SUBSCR <-------------------- 14 JUMP_ABSOLUTE 6 >> 16 POP_BLOCK >> 18 LOAD_CONST 1 (None) 20 RETURN_VALUE
но к моему удивлению, это была не синтаксическая ошибка
по-видимому, все, что работает в обычном задании, например:
назначение полного среза:
>>> for [][:] in []: ... pass ... >>>
подписка
>>> for [2][0] in [42]: ... pass ... >>>
подписка словарь и т. д. были бы действительными целями-кандидатами, с единственным исключением, являющимся прикован задание; хотя, я тайно думаю, что можно приготовить некоторые грязные синтаксис для выполнения цепочки.
я ожидал бы только одиночные идентификаторы и кортежи идентификаторов
я не могу придумать хороший вариант использования ключа словаря в качестве цели. Кроме того, это более читабельно, чтобы сделать назначение ключа словаря в теле цикла, чем использовать его в качестве цели в
for
предложения.тем не менее, расширенная распаковка (Python 3), которая очень полезна в регулярных назначениях, также приходит одинаково удобно в цикле for:
>>> lst = [[1, '', '', 3], [3, '', '', 6]] >>> for x, *y, z in lst: ... print(x,y,z) ... 1 ['', ''] 3 3 ['', ''] 6
соответствующий механизм для назначения различных целей здесь также вызывается; несколько
STORE_NAME
s:>>> dis.dis('for x, *y, z in lst: pass') 1 0 SETUP_LOOP 20 (to 22) 2 LOAD_NAME 0 (lst) 4 GET_ITER >> 6 FOR_ITER 12 (to 20) 8 EXTENDED_ARG 1 10 UNPACK_EX 257 12 STORE_NAME 1 (x) <----- 14 STORE_NAME 2 (y) <----- 16 STORE_NAME 3 (z) <----- 18 JUMP_ABSOLUTE 6 >> 20 POP_BLOCK >> 22 LOAD_CONST 0 (None) 24 RETURN_VALUE
идет, чтобы показать, что A
for
едва ли простые операторы присваивания выполняются последовательно.
следующий код будет иметь смысл, не так ли?
foo = [42] for x in foo: print x
The
for
цикл будет повторяться по спискуfoo
и присвоить каждому объекту имяx
в текущем пространстве имен, в свою очередь. Результатом будет одна итерация и один вывод42
.вместо
x
в коде, у вас естьk['z']
.k['z']
является допустимым именем для хранения. Какx
в моем примере он еще не существует. Это, по сути,k.z
в глобальном пространство имен. Цикл создаетk.z
илиk['z']
и присваивает значения, которые он находит вfoo
ему точно так же он бы создалx
и присвоить ему значения в моем примере. Если бы у вас было больше ценностей в foo...foo = [42, 51, "bill", "ted"] k = {'c': 'd'} for k['z'] in foo: print k
в результате:
{'c': 'd', 'z': 42} {'c': 'd', 'z': 51} {'c': 'd', 'z': 'bill'} {'c': 'd', 'z': 'ted'}
вы написали совершенно правильный случайный код. Это даже не странный код. Вы просто обычно не думаете о словарных статьях как о переменных.
даже если код не странный, как можно позволить такому назначению быть полезным?
key_list = ['home', 'car', 'bike', 'locker'] loc_list = ['under couch', 'on counter', 'in garage', 'in locker'] chain = {} for index, chain[key_list[index]] in enumerate(loc_list): pass
вероятно, не лучший способ сделать это, но помещает два одинаковых списка длины вместе в словарь. Я уверен, что есть и другие вещи, для которых более опытные программисты использовали назначение словарных ключей в циклах for. Возможно...
имя-это просто ключ словаря*.
for x in blah:
- это именно
for vars()['x'] in blah:
* (хотя этот словарь не обязательно должен быть реализован как фактический
dict
объект, в случае некоторых оптимизаций, например, в областях функций).
есть ли ситуация, в которой это действительно полезно?
действительно. Всегда хотел избавиться от
itertools.combinations
?def combinations (pool, repeat): def combinations_recurse (acc, pool, index = 0): if index < len(acc): for acc[index] in pool: yield from combinations_recurse(acc, pool, index + 1) else: yield acc yield from combinations_recurse([pool[0]] * repeat, pool) for comb in combinations([0, 1], 3): print(comb)