web-dev-qa-db-fra.com

Pourquoi les expressions cibles arbitraires sont-elles autorisées dans les boucles for?

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?

60
jtbandes

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

Va montrer qu'un for est des instructions d'affectation à peine simples exécutées successivement.

33
Moses Koledoye

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

27
Alan Leuthard

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

7
Veky

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)
5
Uriel