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 a
y 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 usaraextend
en 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_APPEND
mucho, 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.getsizeof
no podría ser la herramienta adecuada para usar aquí.sys.getsizeof
es 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 :list
newlist.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
extend
generalmente 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
list
construido 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
list
comprensiones, son efectivamente equivalentes aappend
s 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)
append
s ypop
S 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 temporaltuple
de elementos individuales y luegoextend
con 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_Extend
como 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 untuple
con desempaque;tuple
s 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 altuple
final cuando eso es lo que se solicitó.BUILD_LIST
,LIST_EXTEND
para cada cosa para desempaquetar,LIST_APPEND
para elementos individuales), en lugar de cargar todo en la pila antes de construir el conjuntolist
con 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_EXTEND
w / o necesidad de envolverb
en un unotuple
para 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_UNPACK
se acostumbra:Eso se maneja en
ceval.c
, lo que construye una lista vacía y la extiende (cona
):_PyList_Extend
usoslist_extend
:Que llama
list_resize
con 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_extend
realidad 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_exact
que 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]