proyectos ejemplos python iterable-unpacking

python - ejemplos - ¿Por qué es válido asignar a una lista vacía pero no a una tupla vacía?



django (4)

Esto surgió en una reciente charla de PyCon .

La declaración

[] = []

no hace nada significativo, pero tampoco arroja una excepción. Tengo la sensación de que esto se debe a las reglas de desempaque. También puede desempaquetar tuplas con listas, por ejemplo,

[a, b] = [1, 2]

hace lo que esperarías. Como consecuencia lógica, esto también debería funcionar, cuando el número de elementos para descomprimir es 0, lo que explicaría por qué la asignación a una lista vacía es válida. Esta teoría está respaldada por lo que sucede cuando intenta asignar una lista no vacía a una lista vacía:

>>> [] = [1] Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: too many values to unpack

Estaría contento con esta explicación, si lo mismo también sería cierto para las tuplas. Si podemos descomprimir en una lista con 0 elementos, también deberíamos poder descomprimir en una tupla con 0 elementos, ¿no? Sin embargo:

>>> () = () File "<stdin>", line 1 SyntaxError: can''t assign to ()

Parece que las reglas de desempaque no se aplican para las tuplas como lo son para las listas. No puedo pensar en ninguna explicación para esta inconsistencia. ¿Hay alguna razón para este comportamiento?


"Asignar a una lista" es la forma incorrecta de pensarlo.

En todos los casos está desempacando : el intérprete de Python crea una instrucción de desempaque a partir de las tres formas de escribirlo, no hay listas o tuplas involucradas en el lado izquierdo (código cortesía de /u/old-man-prismo ):

>>> def f(): ... iterable = [1, 2] ... a, b = iterable ... (c, d) = iterable ... [e, f] = iterable ... >>> from dis import dis >>> dis(f) 2 0 LOAD_CONST 1 (1) 3 LOAD_CONST 2 (2) 6 BUILD_LIST 2 9 STORE_FAST 0 (iterable) 3 12 LOAD_FAST 0 (iterable) 15 UNPACK_SEQUENCE 2 18 STORE_FAST 1 (a) 21 STORE_FAST 2 (b) 4 24 LOAD_FAST 0 (iterable) 27 UNPACK_SEQUENCE 2 30 STORE_FAST 3 (c) 33 STORE_FAST 4 (d) 5 36 LOAD_FAST 0 (iterable) 39 UNPACK_SEQUENCE 2 42 STORE_FAST 5 (e) 45 STORE_FAST 6 (f) 48 LOAD_CONST 0 (None) 51 RETURN_VALUE

Como puede ver, las tres declaraciones son exactamente iguales.

Lo que hace el desempaquetado ahora es básicamente:

_iterator = iter(some_iterable) a = next(_iterator) b = next(_iterator) for superfluous_element in _iterator: # this only happens if there’s something left raise SyntaxError(''Expected some_iterable to have 2 elements'')

Análogamente para más o menos nombres en el lado izquierdo.

Ahora, como dijo @blckknght: El compilador, por alguna razón, verifica si el lado izquierdo es una tupla vacía y no lo permite, pero no si es una lista vacía.

Es coherente y lógico permitir la asignación de 0 nombres: ¿por qué no? Básicamente, simplemente afirma que el iterable en el lado derecho está vacío. Esa opinión también parece surgir como consenso en el bugs.python.org/issue23275 @gecko mencionado: Vamos a permitir () = iterable .


Decidí tratar de usar dis para descubrir qué está pasando aquí, cuando me tropecé con algo curioso:

>>> def foo(): ... [] = [] ... >>> dis.dis(foo) 2 0 BUILD_LIST 0 3 UNPACK_SEQUENCE 0 6 LOAD_CONST 0 (None) 9 RETURN_VALUE >>> def bar(): ... () = () ... File "<stdin>", line 2 SyntaxError: can''t assign to ()

De alguna manera, el compilador de Python presenta una tupla vacía en el LHS. Esta diferencia varía de la especificación , que establece:

La asignación de un objeto a un solo objetivo se define de forma recursiva de la siguiente manera.

...

  • Si el objetivo es una lista de objetivos encerrada entre paréntesis o entre corchetes: el objeto debe ser iterable con el mismo número de elementos que hay objetivos en la lista de objetivos, y sus elementos se asignan, de izquierda a derecha, al correspondiente objetivos.

Por lo tanto, parece que ha encontrado un error legítimo, aunque en última instancia intrascendente, en CPython (probado 2.7.8 y 3.4.1).

IronPython 2.6.1 exhibe la misma diferencia, pero Jython 2.7b3 + tiene un comportamiento más extraño, con () = () comenzando una declaración aparentemente sin forma de terminarla.


El comentario de @ user2357112 de que esto parece ser una coincidencia parece ser correcto. La parte relevante del código fuente de Python está en Python/ast.c :

switch (e->kind) { # several cases snipped case List_kind: e->v.List.ctx = ctx; s = e->v.List.elts; break; case Tuple_kind: if (asdl_seq_LEN(e->v.Tuple.elts)) { e->v.Tuple.ctx = ctx; s = e->v.Tuple.elts; } else { expr_name = "()"; } break; # several more cases snipped } /* Check for error string set by switch */ if (expr_name) { char buf[300]; PyOS_snprintf(buf, sizeof(buf), "can''t %s %s", ctx == Store ? "assign to" : "delete", expr_name); return ast_error(c, n, buf); }

tuple tienen una comprobación explícita de que la longitud no es cero y generan un error cuando lo es. list no tienen tal verificación, por lo que no se produce ninguna excepción.

No veo ninguna razón en particular para permitir la asignación a una lista vacía cuando es un error asignar a una tupla vacía, pero tal vez hay algún caso especial que no estoy considerando. Sugeriría que este es probablemente un error (trivial) y que los comportamientos deberían ser los mismos para ambos tipos.