58 votes

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

Par accident, j'ai écrit un code comme ceci:

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

Ma conjecture est que le code est traduit littéralement 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 que permet la langue? Je m'attends à ce que seul les identifiants et les tuples de identificateurs doivent être autorisés dans le pour-stmt est l'expression de la cible. Est-il de la situation dans laquelle cela est vraiment utile, et pas seulement une étrange chasse aux sorcières?

31voto

Moses Koledoye Points 60613

L' for boucle suit les règles standard de cession, de sorte que ce qui fonctionne sur la GAUCHE de la vanille, une affectation de travail avec l' for:

Chaque élément est attribué à la liste des cibles à l'aide de la norme règles pour les affectations

L' for construire simplement invoque le mécanisme sous-jacent de 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 telle que la suivante:

plein de tranche d'affectation:

>>> for [][:] in []:
...    pass
... 
>>>

liste d'abonnement

>>> for [2][0] in [42]:
...    pass
... 
>>> 

dictionnaire d'abonnement etc. serait candidat valide les objectifs fixés, à la seule exception étant enchaînés affectation; bien que, je pense secrètement qu'on peut cuire jusqu'à quelques sale syntaxe pour effectuer le chaînage.


Je m'attends à ce que seul les identifiants et les tuples d'identifiants

Je ne peux pas penser à un bon cas d'utilisation d'un dictionnaire clé en tant que cible. En outre, il est plus lisible de faire le dictionnaire de l'affectation de la touche dans le corps de la boucle, de l'utiliser comme une cible dans l' for de la clause.

Toutefois, l'étendue de déballage (Python 3) qui est très utile dans les tâches régulières, est également livré également à portée de main 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 pour l'affectation à des cibles différentes, ici, est également convoqué; 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

Pour montrer qu'un for est à peine une simple affectation des instructions exécutées successivement.

27voto

Alan Leuthard Points 748

Le code suivant serait logique, non?

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

L' for boucle d'itérer sur la liste foo et d'attribuer à chaque objet le nom de l' x en l'espace de noms courant à son tour. Le résultat serait une seule itération et une impression unique d' 42.

À la place de x dans votre code, vous avez k['z']. k['z'] est valable de stockage de nom. Comme x dans mon exemple, il n'existe pas encore. Il est, en effet, k.z en l'espace de noms global. La boucle crée k.z ou k['z'] et affecte les valeurs qu'il trouve dans foo de la même manière, il serait de créer x et assigner 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

aurait pour résultat:

{'c': 'd', 'z': 42}
{'c': 'd', 'z': 51}
{'c': 'd', 'z': 'bill'}
{'c': 'd', 'z': 'ted'}

Vous avez écrit parfaitement valide accidentelle code. Il n'est même pas étrange code. Vous avez juste l'habitude de ne pas penser à des entrées d'un dictionnaire de variables.

Même si le code n'est pas étrange, comment peut permettre une telle cession ne peut-elle ê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 met deux égale à la longueur des listes d'ensemble dans un dictionnaire. Je suis sûr qu'il ya d'autres choses que les programmeurs plus expérimentés ont utilisé des clés du dictionnaire de la cession dans des boucles for pour. Peut-être...

7voto

Veky Points 142

Chaque nom est juste 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 réel dict , dans le cas de certaines optimisations, comme dans les portées de fonctions).

5voto

Uriel Points 10724

Y at-il une situation dans laquelle cela est réellement utile?

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X