誤って次のようなコードを作成しました。
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のターゲット式 では、単一の識別子と識別子のタプルのみが許可されるはずです。奇妙な落とし穴だけでなく、これが実際に役立つ状況はありますか?
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_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
for
が連続して実行されるかろうじて単純な割り当てステートメントであることを示します。
次のコードは理にかなっていますよね?
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ループで辞書キーの割り当てを使用している他のことがあると確信しています。多分...
Every nameは単なる辞書キーです*。
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)