По-видимому list(a)
, не перераспределяет, [x for x in a]
перераспределяет в некоторых точках, и [*a]
перераспределяет все время ?
Вот размеры n от 0 до 12 и результирующие размеры в байтах для трех методов:
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
Вычисляется следующим образом, воспроизводится в repl.it , используя 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]))
Так как это работает? Как [*a]
перераспределить? На самом деле, какой механизм он использует для создания списка результатов из заданного ввода? Он использует итератор a
и использует что-то вроде list.append
? Где находится исходный код?
( Colab с данными и кодом, который произвел изображения.)
Увеличение до меньшего n:
Уменьшение до большего n:
python
python-3.x
list
cpython
python-internals
Стефан Почманн
источник
источник
[*a]
видимому, ведет себя как использованиеextend
в пустом списке.list(a)
работает полностью в C; он может распределять внутренний буферный узел по узлам, когда он повторяетсяa
.[x for x in a]
просто используетLIST_APPEND
много, поэтому он следует обычному шаблону «немного перераспределить, перераспределить при необходимости» обычного списка.[*a]
используетBUILD_LIST_UNPACK
, что ... я не знаю, что это делает, за исключением, по-видимому, чрезмерного распределения все время :)list(a)
и[*a]
они идентичны, и оба слишком распределены по сравнению с[x for x in a]
, так что ...sys.getsizeof
возможно, это не тот инструмент, который можно использовать здесь.sys.getsizeof
это правильный инструмент, он просто показывает, чтоlist(a)
раньше перераспределять. Фактически, что нового в Python 3.8 упоминает это: «Конструктор списка не перераспределяет [...]» .Ответы:
[*a]
внутренне делает C эквивалент :list
newlist.extend(a)
list
.Так что если вы расширите свой тест до:
Попробуйте онлайн!
вы увидите результаты
getsizeof([*a])
иl = []; l.extend(a); getsizeof(l)
одинаковы.Это обычно правильно делать; когда
extend
вы обычно ожидаете добавить больше позже, и аналогично для обобщенной распаковки, предполагается, что несколько вещей будут добавлены одна за другой.[*a]
это не нормальный случай; Python предполагает, что вlist
([*a, b, c, *d]
) добавлено несколько элементов или итераций , поэтому перераспределение экономит работу в общем случае.В отличие от этого,
list
построенный из единственной, прецизионной итерируемой (сlist()
) не может расти или сокращаться во время использования, и перераспределение является преждевременным, пока не доказано обратное; Недавно Python исправил ошибку, из-за которой конструктор перераспределялся даже для входов с известным размером .Что касается
list
понимания, они фактически эквивалентны повторнымappend
s, так что вы видите конечный результат нормальной схемы роста перераспределения при добавлении элемента за раз.Чтобы было ясно, ничего из этого не является языковой гарантией. Это как CPython реализует это. Спецификация языка Python, как правило, не имеет отношения к конкретным моделям роста
list
(кроме гарантии амортизации).O(1)
append
s иpop
s с конца). Как отмечено в комментариях, конкретная реализация снова меняется в 3.9; хотя это не повлияет[*a]
, это может повлиять на другие случаи, когда то, что раньше было «созданием временногоtuple
элемента из отдельных элементов, а затемextend
с помощьюtuple
», теперь становится множеством примененийLIST_APPEND
, которые могут измениться, когда происходит перераспределение и какие числа входят в расчет.источник
BUILD_LIST_UNPACK
, он использует_PyList_Extend
как эквивалент вызова Cextend
(просто напрямую, а не путем поиска метода). Они соединили его с дорожками для сборкиtuple
с распаковкой;tuple
Не нужно перераспределять красиво для частичной сборки, поэтому они всегда распаковываются вlist
(чтобы извлечь выгоду из перераспределения) и конвертируютсяtuple
в конец, когда это то, что было запрошено.BUILD_LIST
,LIST_EXTEND
для каждой вещи распаковывать,LIST_APPEND
для отдельных элементов), вместо загрузки всего на стеке перед сборкой всеlist
с одной командой байт - коды (это позволяет компилятор для выполнения оптимизаций , что команда все-в-одном не позволяет, как осуществление ,[*a, b, *c]
какLIST_EXTEND
,LIST_APPEND
,LIST_EXTEND
ж / о необходимости завернутьb
в одно ,tuple
чтобы соответствовать требованиямBUILD_LIST_UNPACK
).Полная картина того, что происходит, основываясь на других ответах и комментариях (особенно ответ ShadowRanger , который также объясняет, почему это так).
Разборка показывает что
BUILD_LIST_UNPACK
привыкнет:Это обрабатывается в
ceval.c
, который создает пустой список и расширяет его (сa
):_PyList_Extend
используетlist_extend
:Который звонит
list_resize
с суммой размеров :И это в целом распределяется следующим образом:
Давайте проверим это. Вычислите ожидаемое количество пятен с помощью приведенной выше формулы и вычислите ожидаемый размер байта, умножив его на 8 (как я использую здесь 64-битный Python) и добавив размер байта пустого списка (то есть постоянные издержки объекта списка) :
Вывод:
Соответствует за исключением того
n = 0
, что наlist_extend
самом деле ярлыки , так что на самом деле это тоже совпадаетисточник
Они будут деталями реализации интерпретатора CPython и поэтому могут быть непоследовательными для других интерпретаторов.
Тем не менее, вы можете увидеть, откуда
list(a)
приходит понимание и поведение:https://github.com/python/cpython/blob/master/Objects/listobject.c#L36
Специально для понимания:
Прямо под этими строками есть то,
list_preallocate_exact
что используется при звонкеlist(a)
.источник
[*a]
не добавляет отдельные элементы по одному. У него есть собственный выделенный байт-код, который выполняет массовую вставку черезextend
.[*a]