Разница между "[:]" и "[::]" разрезанием при копировании списка?

Мы можем (неглубоко) скопировать a list с помощью [:]:

l = [1, 2, 3]
z1 = l[:]

Мы также можем (мелкой) скопировать его с помощью [::]:

z2 = l[::]

а теперь z1 == z2 будет True. Я понимаю, как эти фрагменты работают после прочтения ответов в Объяснить нотацию фрагмента Python.

Но, мой вопрос, есть ли разница между этими двумя внутренне? Является ли более эффективным, чем другое, при копировании или делает то же самое?

Ответ 1

Абсолютно никакой разницы между ними, по крайней мере, на Python 3. Вы можете проверить байт-код, созданный для каждого из них, используя dis.dis, если хотите:

l = [1, 2, 3, 4]

Байт-код, испущенный для l[:]:

from dis import dis
dis('l[:]')
  1           0 LOAD_NAME                0 (l)
              3 LOAD_CONST               0 (None)
              6 LOAD_CONST               0 (None)
              9 BUILD_SLICE              2
             12 BINARY_SUBSCR
             13 RETURN_VALUE

а байт-код, испущенный для l[::]:

dis('l[::]')
  1           0 LOAD_NAME                0 (l)
              3 LOAD_CONST               0 (None)
              6 LOAD_CONST               0 (None)
              9 BUILD_SLICE              2
             12 BINARY_SUBSCR
             13 RETURN_VALUE

как вы можете видеть, они точно такие же. Оба загружают некоторые None (два LOAD_CONSTS 's) для значений start и stop, используемых для создания среза (BUILD_SLICE) и применяют его. None являются стандартными для них, как указано в документах для slices в иерархии стандартного типа:

Специальные атрибуты только для чтения: start - это привязка lower; stop - верхняя граница; step - значение step; каждый из них None, если его опущено. Эти атрибуты могут иметь любой тип.

Используйте [:], это меньше нажатий клавиш.


На самом деле интересно отметить, что в Python 2.x генерируемый байт-код отличается и из-за меньших команд для l[:] он может быть немного более результативным:

>>> def foo():
...     l[:]
... 
>>> dis(foo)
  2           0 LOAD_GLOBAL              0 (l)
              3 SLICE+0             
              4 POP_TOP             
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE 

Пока для l[::]:

>>> def foo2():
...     l[::]
... 
>>> dis(foo2)
  2           0 LOAD_GLOBAL              0 (l)
              3 LOAD_CONST               0 (None)
              6 LOAD_CONST               0 (None)
              9 LOAD_CONST               0 (None)
             12 BUILD_SLICE              3
             15 BINARY_SUBSCR       
             16 POP_TOP             
             17 LOAD_CONST               0 (None)
             20 RETURN_VALUE 

Несмотря на то, что я не приурочил их (и я не буду, разница должна быть крошечной), кажется, что из-за просто требуемых инструкций l[:] может быть немного лучше.


Это сходство, конечно, не существует только для списков; он применяется ко всем последовательностям в Python:

# Note: the Bytecode class exists in Py > 3.4
>>> from dis import Bytecode
>>>
>>> Bytecode('(1, 2, 3)[:]').dis() == Bytecode('(1, 2, 3)[::]').dis() 
True
>>> Bytecode('"string"[:]').dis() == Bytecode('"string"[::]').dis() 
True

аналогично для других.

Ответ 2

В Раздел ссылок на язык Python 6.3.2, Подписки, внутреннее выражение для последовательности должно оценивать либо целое число, либо фрагмент, Оба эти примера производят один и тот же срез и поэтому идентичны. Есть также множество других срезов, которые имеют одинаковый эффект, явно указывая значения по умолчанию (start=0, stop=len(sequence) или больше, step=1).