Aparentemente list(a)
, o macacão não é atribuído, o [x for x in a]
macacão em alguns pontos e o [*a]
macacão o tempo todo ?
Aqui estão os tamanhos n de 0 a 12 e os tamanhos resultantes em bytes para os três 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
Computado assim, reproduzível em 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]))
Então, como isso funciona? Como o [*a]
globalocate? Na verdade, qual mecanismo ele usa para criar a lista de resultados a partir da entrada fornecida? Ele usa um iterador a
e usa algo comolist.append
? Onde está o código fonte?
( Colab com dados e código que produziu as imagens.)
Aumentando o zoom para n menor:
Diminuindo o zoom para n maior:
python
python-3.x
list
cpython
python-internals
Stefan Pochmann
fonte
fonte
[*a]
parece se comportar como o usoextend
em uma lista vazia.list(a)
opera inteiramente em C; ele pode alocar o nó do buffer interno por nó à medida que iteraa
.[x for x in a]
apenas usaLIST_APPEND
muito, de modo que segue o padrão normal de "alocar um pouco, realocar quando necessário" de uma lista normal.[*a]
usaBUILD_LIST_UNPACK
, o que ... Eu não sei o que isso faz, além de aparentementelist(a)
e[*a]
são idênticos, e a localização geral comparada a[x for x in a]
, então ...sys.getsizeof
pode não ser a ferramenta certa a ser usada aqui.sys.getsizeof
é a ferramenta certa, apenas mostra quelist(a)
costumava ser usada em geral. Na verdade, o que há de novo no Python 3.8 menciona: "O construtor da lista não atribui [...] globalmente" .Respostas:
[*a]
está fazendo internamente o equivalente C de :list
newlist.extend(a)
list
.Portanto, se você expandir seu teste para:
Experimente online!
você verá os resultados
getsizeof([*a])
el = []; l.extend(a); getsizeof(l)
é o mesmo.Geralmente é a coisa certa a fazer; quando
extend
você espera adicionar mais tarde e da mesma forma para descompactação generalizada, assume-se que várias coisas serão adicionadas uma após a outra.[*a]
não é o caso normal; O Python assume que há vários itens ou iteráveis sendo adicionados aolist
([*a, b, c, *d]
), portanto, a alocação geral economiza trabalho no caso comum.Por outro lado, um
list
construído a partir de uma iterável preset única (comlist()
) pode não crescer ou encolher durante o uso, e a localização geral é prematura até prova em contrário; Recentemente, o Python corrigiu um bug que fazia o construtor se localizar globalmente, mesmo para entradas com tamanho conhecido .Quanto às
list
compreensões, elas são efetivamente equivalentes a repetidasappend
s ; portanto, você está vendo o resultado final do padrão de crescimento da alocação geral ao adicionar um elemento de cada vez.Para ser claro, nada disso é uma garantia de idioma. É exatamente como o CPython o implementa. A especificação da linguagem Python geralmente não se preocupa com padrões de crescimento específicos em
list
(além de garantir seO(1)
append
e s amortizadospop
no final). Conforme observado nos comentários, a implementação específica muda novamente em 3.9; Embora isso não afete[*a]
, ele pode afetar outros casos em que o que costumava ser "constrói umtuple
item temporário de itens individuais e depoisextend
com otuple
" agora se torna várias aplicações deLIST_APPEND
, o que pode mudar quando a alocação geral ocorre e quais números entram no cálculo.fonte
BUILD_LIST_UNPACK
, ele usa_PyList_Extend
como o equivalente C da chamadaextend
(apenas diretamente, e não pela pesquisa de método). Eles combinaram isso com os caminhos para a construção de umtuple
com desembalagem;tuple
s não atribuem um bom desempenho geral à construção fragmentada; portanto, sempre descompactam em umlist
(para se beneficiar da localização geral) e convertem-setuple
no final quando é o que foi solicitado.BUILD_LIST
,LIST_EXTEND
para cada coisa a descompactar,LIST_APPEND
para itens individuais), em vez de carregar tudo na pilha antes de construir o todolist
com uma única instrução código de byte (que permite que o compilador para realizar otimizações que a instrução all-in-one não permitiu, como a implementação[*a, b, *c]
comoLIST_EXTEND
,LIST_APPEND
,LIST_EXTEND
w / o a necessidade de envolverb
em um um-tuple
para atender aos requisitos deBUILD_LIST_UNPACK
).Imagem completa do que acontece, aproveitando as outras respostas e comentários (especialmente a resposta do ShadowRanger , que também explica por que é assim).
Desmontar mostra que
BUILD_LIST_UNPACK
é usado:Isso é tratado em
ceval.c
que constrói uma lista vazia e estende-lo (coma
):_PyList_Extend
usalist_extend
:Que chama
list_resize
com a soma dos tamanhos :E isso totaliza da seguinte maneira:
Vamos verificar isso. Calcule o número esperado de pontos com a fórmula acima e calcule o tamanho esperado de bytes multiplicando-o por 8 (como estou usando Python de 64 bits aqui) e adicionando o tamanho de bytes de uma lista vazia (ou seja, a sobrecarga constante de um objeto de lista) :
Resultado:
Corresponde a exceção
n = 0
, quelist_extend
na verdade é um atalho , e na verdade isso também corresponde:fonte
Esses serão detalhes de implementação do interpretador CPython e, portanto, podem não ser consistentes com outros intérpretes.
Dito isto, você pode ver onde a compreensão e os
list(a)
comportamentos entram aqui:https://github.com/python/cpython/blob/master/Objects/listobject.c#L36
Especificamente para a compreensão:
Logo abaixo dessas linhas, existe o
list_preallocate_exact
que é usado ao chamarlist(a)
.fonte
[*a]
não anexa elementos individuais, um de cada vez. Ele tem seu próprio bytecode dedicado, que faz a inserção em massa viaextend
.[*a]