with tutorial the español applications python for-loop language-lawyer

python - tutorial - ¿Por qué se permiten expresiones de destino arbitrarias en for-loops?



the django project (4)

¿Hay alguna situación en la que esto sea realmente útil?

En efecto. ¿Alguna vez quisiste deshacerte 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)

Accidentalmente escribí un código como este:

foo = [42] k = {''c'': ''d''} for k[''z''] in foo: # Huh?? print k

Pero para mi sorpresa, esto no fue un error de sintaxis. En su lugar, imprime {''c'': ''d'', ''z'': 42} .

Supongo que el código se traduce literalmente a algo como:

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

Pero ... ¿por qué el lenguaje lo permite? Esperaría que solo se permitieran identificadores individuales y tuplas de identificadores en la expresión de destino de for-stmt . ¿Hay alguna situación en la que esto sea realmente útil, no solo un problema extraño?


El bucle for sigue las reglas estándar de asignación, por lo que lo que funciona en el LHS de una asignación vainilla debería funcionar con for :

Cada elemento a su vez se asigna a la lista de objetivos utilizando las reglas estándar para las asignaciones

La construcción for simplemente invoca el mecanismo subyacente para asignar al objetivo que en el caso de su código de muestra es 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

Pero para mi sorpresa, esto no fue un error de sintaxis

Aparentemente, lo que sea que funcione en una tarea regular como la siguiente:

asignación de corte completa :

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

lista de suscripción

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

la suscripción al diccionario, etc. serían objetivos candidatos válidos, con la única excepción de una asignación encadenada ; aunque, secretamente, creo que uno puede cocinar una sintaxis sucia para realizar el encadenamiento.

Esperaría solo identificadores únicos y tuplas de identificadores

No puedo pensar en un buen caso de uso para una clave de diccionario como objetivo. Además, es más legible hacer la asignación de la clave del diccionario en el cuerpo del bucle que usarlo como un objetivo en la cláusula for .

Sin embargo, el desempaquetado extendido (Python 3) que es muy útil en las tareas regulares también es igualmente útil en un ciclo for:

>>> lst = [[1, '''', '''', 3], [3, '''', '''', 6]] >>> for x, *y, z in lst: ... print(x,y,z) ... 1 ['''', ''''] 3 3 ['''', ''''] 6

Aquí también se invoca el mecanismo correspondiente para asignar a los diferentes objetivos; múltiples 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

Demuestra que un for es apenas declaraciones de asignación simples ejecutadas sucesivamente.


El siguiente código tendría sentido, ¿verdad?

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

El bucle for iteraría sobre la lista foo y asignaría cada objeto al nombre x en el espacio de nombres actual a su vez. El resultado sería una única iteración y una impresión única de 42 .

En lugar de x en su código, tiene k[''z''] . k[''z''] es un nombre de almacenamiento válido. Como x en mi ejemplo, todavía no existe. Es, en efecto, kz en el espacio de nombres global. El bucle crea kz o k[''z''] y le asigna los valores que encuentra en foo de la misma manera que crearía x y le asignaría los valores en mi ejemplo. Si tuvieras más valores en foo ...

foo = [42, 51, "bill", "ted"] k = {''c'': ''d''} for k[''z''] in foo: print k

daría como resultado:

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

Escribiste un código accidental perfectamente válido. Ni siquiera es un código extraño. Por lo general, no piensas en las entradas del diccionario como variables.

Incluso si el código no es extraño, ¿cómo puede ser útil permitir tal asignación?

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

Probablemente no sea la mejor manera de hacerlo, pero reúne dos listas de igual longitud en un diccionario. Estoy seguro de que hay otras cosas en las que los programadores más experimentados han utilizado la asignación de teclas del diccionario para bucles. Tal vez...


Cada nombre es solo una clave de diccionario *.

for x in blah:

es precisamente

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

* (aunque ese diccionario no necesita implementarse como un objeto dict real, en el caso de algunas optimizaciones, como en los ámbitos de funciones).