Подтвердить что ты не робот

Почему произвольные целевые выражения разрешены для for-loops?

Я случайно написал такой код:

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 целевом выражении должно быть разрешено только одиночные идентификаторы и кортежи идентификаторов. Есть ли какая-то ситуация, в которой это действительно полезно, а не просто странная добыча?

4b9b3361

Ответ 1

Цикл for следует стандартным правилам назначения, поэтому то, что работает на LHS имени ванили, должно работать с for:

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

Конструкция 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

Но, к моему удивлению, это была не синтаксическая ошибка

По-видимому, все, что работает в регулярном назначении, такое как:

полное назначение slice:

>>> 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

Также вызван соответствующий механизм назначения для разных целей цели; multiple 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 - это просто простые операторы присваивания, выполняемые последовательно.

Ответ 2

Следующий код имеет смысл, правильно?

foo = [42]
for x in foo:
    print x

Цикл 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

Наверное, это не лучший способ сделать это, но вместе с двумя списками одинаковой длины в словарь. Я уверен, что есть и другие вещи, которые более опытные программисты использовали для использования в качестве шлейфов для ключевых слов. Может быть,...

Ответ 3

Каждое имя является просто словарным ключом *.

for x in blah:

точно

for vars()['x'] in blah:

* (хотя этот словарь не должен быть реализован как фактический объект dict, в случае некоторых оптимизаций, например, в области функций).

Ответ 4

Есть ли какая-нибудь ситуация, когда это действительно полезно?

Действительно. Когда-либо хотелось избавиться от 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)