Wygląda na list(a)
to, że totalocate, [x for x in a]
totalocates w niektórych momentach i [*a]
totalocate przez cały czas ?
Oto rozmiary n od 0 do 12 i wynikowe rozmiary w bajtach dla trzech metod:
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
Obliczony w ten sposób, odtwarzalny w repl.it , przy użyciu 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]))
Więc: jak to działa? Jak działa [*a]
totalocate? Jakiego mechanizmu używa do tworzenia listy wyników na podstawie danych wejściowych? Czy używa iteratora a
i używa czegoś takiego list.append
? Gdzie jest kod źródłowy?
( Colab z danymi i kodem, który wytworzył obrazy).
Powiększanie do mniejszego n:
Pomniejszanie do większego n:
python
python-3.x
list
cpython
python-internals
Stefan Pochmann
źródło
źródło
[*a]
wydaje się zachowywać jak używanieextend
na pustej liście.list(a)
działa całkowicie w C; może przydzielać wewnętrzny węzeł buforowy po węźle podczas iteracjia
.[x for x in a]
po prostuLIST_APPEND
dużo zużywa , więc podąża za normalnym wzorcem „ogólnie przydzielać trochę, w razie potrzeby ponownie przydzielać” normalnej listy.[*a]
używaBUILD_LIST_UNPACK
, które ... nie wiem, co to robi, poza tym, że najwyraźniej cały czaslist(a)
i[*a]
są identyczne, a obie overallocate porównaniu[x for x in a]
, więc ...sys.getsizeof
nie może być właściwym narzędziem do wykorzystania tutaj.sys.getsizeof
to właściwe narzędzie, to po prostu pokazuje, żelist(a)
używałeś do generalocate. Właściwie to, co nowego w Python 3.8 wspomina: „Konstruktor listy nie dokonuje ogólnej alokacji [...]” .Odpowiedzi:
[*a]
wykonuje wewnętrznie C odpowiednik :list
newlist.extend(a)
list
.Jeśli więc rozszerzysz swój test na:
Wypróbuj online!
zobaczysz wyniki dla
getsizeof([*a])
il = []; l.extend(a); getsizeof(l)
są takie same.Zazwyczaj jest to właściwe postępowanie; podczas
extend
ing zazwyczaj spodziewasz się dodać więcej później, i podobnie w przypadku ogólnego rozpakowywania, zakłada się, że wiele rzeczy zostanie dodanych jeden po drugim.[*a]
to nie jest normalny przypadek; Python zakłada, że dolist
([*a, b, c, *d]
) jest dodawanych wiele elementów lub iteracji , więc ogólna alokacja oszczędza pracę w typowym przypadku.W przeciwieństwie do tego,
list
skonstruowany z jednego, zalecanego iterowalnego (zlist()
) może nie rosnąć lub kurczyć się podczas użytkowania, a ogólne przydziały są przedwczesne, dopóki nie zostanie udowodnione inaczej; Ostatnio Python naprawił błąd, który spowodował, że konstruktor ogólnie przydzielił się nawet dla danych wejściowych o znanym rozmiarze .Jeśli chodzi o
list
rozumienie, są one faktycznie równoważne powtarzanymappend
s, więc widzisz końcowy wynik normalnego wzorca wzrostu ogólnej alokacji podczas dodawania elementu na raz.Żeby było jasne, nic z tego nie stanowi gwarancji językowej. Tak właśnie implementuje to CPython. Języku specyfikacji Python ogół obojętny konkretnych wzorców wzrostu w
list
(oprócz poręczającym amortyzowaneO(1)
append
S ipop
S z końcu). Jak zauważono w komentarzach, konkretne wdrożenie ponownie zmienia się w wersji 3.9; chociaż nie będzie to miało wpływu[*a]
, może to wpłynąć na inne przypadki, w których to, co kiedyś było „buduje tymczasowetuple
pojedyncze elementy, a następnieextend
ztuple
„ teraz staje się wieloma aplikacjamiLIST_APPEND
, które mogą się zmienić, gdy wystąpi ogólna alokacja i jakie liczby zostaną uwzględnione w obliczeniach.źródło
BUILD_LIST_UNPACK
, który używa_PyList_Extend
jako równoważnika C wywoływaniaextend
(tylko bezpośrednio, a nie poprzez wyszukiwanie metod). Połączyli go ze ścieżkami budowaniatuple
z rozpakowaniem;tuple
nie ładują się ogólnie przy budowaniu fragmentarycznym, więc zawsze rozpakowują się nalist
(aby skorzystać z ogólnej alokacji) i przechodzątuple
na koniec, kiedy jest to wymagane.BUILD_LIST
,LIST_EXTEND
dla każdego coś się rozpakować,LIST_APPEND
dla pojedynczych elementów), zamiast ładowania wszystkiego na stosie przed budową całylist
z jednej instrukcji kodu bajtowego (to pozwala kompilator wykonywać optymalizacje że instrukcja all-in-one nie pozwalają, jak wdrożenie[*a, b, *c]
jakLIST_EXTEND
,LIST_APPEND
,LIST_EXTEND
w / o konieczności owinąćb
w jedno-tuple
do spełnienia wymagańBUILD_LIST_UNPACK
).Pełny obraz tego, co się dzieje, w oparciu o inne odpowiedzi i komentarze (zwłaszcza odpowiedź ShadowRanger , która wyjaśnia również, dlaczego tak się dzieje).
Demontaż programów, które
BUILD_LIST_UNPACK
są używane:Który jest obsługiwany w
ceval.c
, która buduje pustą listę i rozciąga go (za
):_PyList_Extend
wykorzystujelist_extend
:Który wywołuje
list_resize
z sumą rozmiarów :I to ogólnie rzecz biorąc, co następuje:
Sprawdźmy to. Oblicz oczekiwaną liczbę miejsc za pomocą powyższej formuły i oblicz oczekiwany rozmiar bajtu, mnożąc go przez 8 (ponieważ używam tutaj 64-bitowego Pythona) i dodając rozmiar bajtu pustej listy (tj. Stały narzut obiektu listy) :
Wynik:
Dopasowuje oprócz tych
n = 0
, którelist_extend
faktycznie są skrótami , więc tak naprawdę to też pasuje:źródło
Będą to szczegóły implementacji interpretera CPython, więc mogą nie być spójne dla innych tłumaczy.
To powiedziawszy, możesz zobaczyć, gdzie
list(a)
przychodzi zrozumienie i zachowania:https://github.com/python/cpython/blob/master/Objects/listobject.c#L36
Specjalnie do zrozumienia:
Tuż pod tymi liniami jest to,
list_preallocate_exact
które jest używane podczas połączenialist(a)
.źródło
[*a]
nie dodaje pojedynczych elementów pojedynczo. Posiada własny dedykowany kod bajtowy, który wykonuje wstawianie zbiorcze za pośrednictwemextend
.[*a]