Al parecer, list(a)¿no se sobreasigna, se [x for x in a]sobreasigna en algunos puntos y se [*a]sobreasigna todo el tiempo ?
Estos son los tamaños n de 0 a 12 y los tamaños resultantes en bytes para los tres métodos:
0 56 56 56
1 64 88 88
2 72 88 96
3 80 88 104
4 88 88 112
5 96 120 120
6 104 120 128
7 112 120 136
8 120 120 152
9 128 184 184
10 136 184 192
11 144 184 200
12 152 184 208
Calculado así, reproducible en repl.it , usando Python 3. 8 :
from sys import getsizeof
for n in range(13):
a = [None] * n
print(n, getsizeof(list(a)),
getsizeof([x for x in a]),
getsizeof([*a]))
¿Entonces, cómo funciona esto? ¿Cómo se [*a]sobreasigna? En realidad, ¿qué mecanismo utiliza para crear la lista de resultados a partir de la entrada dada? ¿Utiliza un iterador ay usa algo como list.append? ¿Dónde está el código fuente?
( Colab con datos y código que produjeron las imágenes).
Acercamiento a n menor:
Alejar a n mayor:
python
python-3.x
list
cpython
python-internals
Stefan Pochmann
fuente
fuente




[*a]parece comportarse como si se usaraextenden una lista vacía.list(a)opera completamente en C; puede asignar el búfer interno nodo por nodo a medida que se repitea.[x for x in a]simplemente usaLIST_APPENDmucho, por lo que sigue el patrón normal de "sobreasignar un poco, reasignar cuando sea necesario" de una lista normal.[*a]usosBUILD_LIST_UNPACK, que ... No sé qué hace eso, aparte de aparentementelist(a)y[*a]son idénticos, y tanto overallocate en comparación con[x for x in a], por lo que ...sys.getsizeofno podría ser la herramienta adecuada para usar aquí.sys.getsizeofes la herramienta correcta, solo muestra quelist(a)solía sobreasignar. En realidad, lo nuevo en Python 3.8 lo menciona: "El constructor de la lista no sobreasigna [...]" .Respuestas:
[*a]está haciendo internamente el equivalente en C de :listnewlist.extend(a)list.Entonces, si expande su prueba a:
Pruébalo en línea!
verá los resultados
getsizeof([*a])yl = []; l.extend(a); getsizeof(l)son los mismos.Esto suele ser lo correcto; cuando
extendgeneralmente espera agregar más más tarde, y de manera similar para el desempaque generalizado, se supone que se agregarán varias cosas una tras otra.[*a]no es el caso normal; Python asume que hay múltiples elementos o iterables que se agregan alist([*a, b, c, *d]), por lo que la sobreasignación ahorra trabajo en el caso común.Por el contrario, un
listconstruido a partir de un único iterativo preestablecido (conlist()) puede no crecer o encogerse durante el uso, y la sobreasignación es prematura hasta que se demuestre lo contrario; Python recientemente corrigió un error que hacía que el constructor se sobreasignara incluso para entradas con un tamaño conocido .En cuanto a las
listcomprensiones, son efectivamente equivalentes aappends repetidas , por lo que está viendo el resultado final del patrón de crecimiento de sobreasignación normal al agregar un elemento a la vez.Para ser claros, nada de esto es una garantía de idioma. Así es como CPython lo implementa. La especificación de lenguaje Python es generalmente despreocupado con patrones de crecimiento específicos en
list(aparte de garantizar amortizadoO(1)appends ypopS desde el final). Como se señaló en los comentarios, la implementación específica cambia nuevamente en 3.9; Si bien no afectará[*a], podría afectar otros casos en los que lo que solía ser "construir un elemento temporaltuplede elementos individuales y luegoextendcon eltuple" ahora se convierte en múltiples aplicaciones deLIST_APPEND, que pueden cambiar cuando se produce la sobreasignación y qué números entran en el cálculo.fuente
BUILD_LIST_UNPACK, se usa_PyList_Extendcomo el equivalente en C de la llamadaextend(solo directamente, en lugar de por método de búsqueda). Lo combinaron con los caminos para construir untuplecon desempaque;tuples no se sobreasigna muy bien para la construcción fragmentaria, por lo que siempre se descomprimen en unlist(para beneficiarse de la sobreasignación) y se convierten altuplefinal cuando eso es lo que se solicitó.BUILD_LIST,LIST_EXTENDpara cada cosa para desempaquetar,LIST_APPENDpara elementos individuales), en lugar de cargar todo en la pila antes de construir el conjuntolistcon una sola instrucción de código de bytes (permite el compilador para realizar optimizaciones que la instrucción de todo-en-uno no permitió que, al igual que la implementación[*a, b, *c]comoLIST_EXTEND,LIST_APPEND,LIST_EXTENDw / o necesidad de envolverben un unotuplepara cumplir con los requisitos deBUILD_LIST_UNPACK).Imagen completa de lo que sucede, basándose en las otras respuestas y comentarios (especialmente la respuesta de ShadowRanger , que también explica por qué se hace así).
Desmontaje muestra que
BUILD_LIST_UNPACKse acostumbra:Eso se maneja en
ceval.c, lo que construye una lista vacía y la extiende (cona):_PyList_Extendusoslist_extend:Que llama
list_resizecon la suma de los tamaños :Y eso se sobreasigna de la siguiente manera:
Vamos a ver eso. Calcule el número esperado de puntos con la fórmula anterior y calcule el tamaño de byte esperado multiplicándolo con 8 (ya que estoy usando Python de 64 bits aquí) y agregando el tamaño de byte de una lista vacía (es decir, la sobrecarga constante de un objeto de lista) :
Salida:
Coincide con excepción de
n = 0, que enlist_extendrealidad atajos , por lo que también coincide:fuente
Estos serán detalles de implementación del intérprete de CPython y, por lo tanto, pueden no ser coherentes con otros intérpretes.
Dicho esto, puedes ver dónde
list(a)entran aquí la comprensión y los comportamientos:https://github.com/python/cpython/blob/master/Objects/listobject.c#L36
Específicamente para la comprensión:
Justo debajo de esas líneas, hay una
list_preallocate_exactque se usa al llamarlist(a).fuente
[*a]no agrega elementos individuales de uno en uno. Tiene su propio código de bytes dedicado, que realiza la inserción masiva a través deextend.[*a]