J'ai accidentellement écrit du code comme celui-ci:
foo = [42]
k = {'c': 'd'}
for k['z'] in foo: # Huh??
print k
Mais à ma grande surprise, ce n'était pas une erreur de syntaxe. Au lieu de cela, il imprime {'c': 'd', 'z': 42}
.
Mon devinez est que le code est traduit littéralement en quelque chose comme:
i = iter(foo)
while True:
try:
k['z'] = i.next() # literally translated to assignment; modifies k!
print k
except StopIteration:
break
Mais ... pourquoi est-ce permis par la langue? Je m'attendrais à ce que seuls les identifiants uniques et les tuples d'identifiants soient autorisés dans le expression cible de for-stmt . Y a-t-il une situation dans laquelle cela est réellement utile, pas seulement un bizarre accroc?
La boucle for
suit les règles d'affectation standard, donc ce qui fonctionne sur le LHS d'une affectation Vanilla devrait fonctionner avec le for
:
Chaque élément est à son tour affecté à la liste cible en utilisant les règles standard pour les affectations
La construction for
appelle simplement le mécanisme sous-jacent pour l'attribution à la cible qui dans le cas de votre exemple de code est 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
Mais à ma grande surprise, ce n'était pas une erreur de syntaxe
Apparemment, tout ce qui fonctionne dans une affectation régulière tel que le suivant:
affectation de tranche complète:
>>> for [][:] in []:
... pass
...
>>>
liste des abonnements
>>> for [2][0] in [42]:
... pass
...
>>>
l'abonnement au dictionnaire, etc. serait une cible candidate valide, à la seule exception étant affectation chaînée; cependant, je pense secrètement que l'on peut préparer une syntaxe sale pour effectuer le chaînage.
Je n'attendrais que des identifiants uniques et des tuples d'identifiants
Je ne peux pas penser à un bon cas d'utilisation pour une clé de dictionnaire comme cible. De plus, il est plus lisible de faire l'affectation des clés du dictionnaire dans le corps de la boucle que de l'utiliser comme cible dans la clause for
.
Cependant, le déballage étendu (Python 3) qui est très utile dans les affectations régulières est également très pratique dans une boucle for:
>>> lst = [[1, '', '', 3], [3, '', '', 6]]
>>> for x, *y, z in lst:
... print(x,y,z)
...
1 ['', ''] 3
3 ['', ''] 6
Le mécanisme correspondant d'attribution aux différentes cibles ici est également invoqué; plusieurs 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
Va montrer qu'un for
est des instructions d'affectation à peine simples exécutées successivement.
Le code suivant aurait du sens, non?
foo = [42]
for x in foo:
print x
La boucle for
itérerait sur la liste foo
et affecterait tour à tour chaque objet au nom x
dans l'espace de noms courant. Le résultat serait une seule itération et une seule impression de 42
.
Au lieu de x
dans votre code, vous avez k['z']
. k['z']
est un nom de stockage valide. Comme x
dans mon exemple, il n'existe pas encore. C'est, en effet, k.z
dans l'espace de noms global. La boucle crée k.z
ou k['z']
et lui attribue les valeurs qu'il trouve dans foo
de la même manière qu'il créerait x
et lui assignerait les valeurs dans mon exemple. Si vous aviez plus de valeurs dans foo ...
foo = [42, 51, "bill", "ted"]
k = {'c': 'd'}
for k['z'] in foo:
print k
entraînerait:
{'c': 'd', 'z': 42}
{'c': 'd', 'z': 51}
{'c': 'd', 'z': 'bill'}
{'c': 'd', 'z': 'ted'}
Vous avez écrit un code accidentel parfaitement valide. Ce n'est même pas un code étrange. Vous ne pensez généralement pas aux entrées de dictionnaire comme des variables.
Même si le code n'est pas étrange, comment autoriser une telle affectation peut-il être utile?
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
Probablement pas la meilleure façon de le faire, mais rassemble deux listes de longueur égale dans un dictionnaire. Je suis sûr qu'il y a d'autres choses que des programmeurs plus expérimentés ont utilisé pour les boucles d'affectation de dictionnaire. Peut être...
Chaque nom n'est qu'une clé de dictionnaire *.
for x in blah:
est précisément
for vars()['x'] in blah:
* (bien que ce dictionnaire n'ait pas besoin d'être implémenté en tant qu'objet dict
réel, dans le cas de certaines optimisations, comme dans les étendues de fonction).
Y a-t-il une situation dans laquelle cela est réellement utile?
En effet. Vous avez toujours voulu vous débarrasser de 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)