web-dev-qa-db-ja.com

forループで任意のターゲット式が許可されるのはなぜですか?

誤って次のようなコードを作成しました。

foo = [42]
k = {'c': 'd'}

for k['z'] in foo:  # Huh??
    print k

しかし、驚いたことに、これは構文エラーではありませんでした。代わりに、{'c': 'd', 'z': 42}

私のguessは、コードが文字通り次のようなものに翻訳されることです:

i = iter(foo)
while True:
    try:
        k['z'] = i.next()  # literally translated to assignment; modifies k!
        print k
    except StopIteration:
        break

しかし...言語でこれが許可されているのはなぜですか? for-stmtのターゲット式 では、単一の識別子と識別子のタプルのみが許可されるはずです。奇妙な落とし穴だけでなく、これが実際に役立つ状況はありますか?

60
jtbandes

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

しかし、驚いたことに、これは構文エラーではありませんでした

どうやら、次のような通常の割り当てで機能するもの:

フルスライス割り当て

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

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

forが連続して実行されるかろうじて単純な割り当てステートメントであることを示します。

33
Moses Koledoye

次のコードは理にかなっていますよね?

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

おそらく最善の方法ではありませんが、2つの等しい長さのリストをまとめて辞書に入れます。私は、経験豊富なプログラマーがforループで辞書キーの割り当てを使用している他のことがあると確信しています。多分...

27
Alan Leuthard

Every nameは単なる辞書キーです*。

for x in blah:

正確に

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

*(ただし、関数スコープなどの最適化の場合、その辞書は実際のdictオブジェクトとして実装する必要はありません).

7
Veky

これが実際に役立つ状況はありますか?

確かに。これまで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)
5
Uriel