We can (shallow) copy a list
by using [:]
:
l = [1, 2, 3]
z1 = l[:]
We can also (shallow) copy it by using [::]
:
z2 = l[::]
and now z1 == z2
will be True
. I understand how these slices work after reading the answers in Explain Python's slice notation.
But, my question is, is there any difference between these two internally? Is one more efficient than the other in copying or do they do exactly the same things?
Use slicing List slicing can be used to easily make a copy of a list. This method is called cloning. The original list will remain unchanged. In this method, slicing is used to copy each element of the original list into the new list.
List slicing returns a new list from the existing list. If Lst is a list, then the above expression returns the portion of the list from index Initial to index End, at a step size IndexJump.
First: Copying by SlicingWhen you omit the start index and the end index from the slice, then your slice will start from the beginning of the list all the way to the end of the list. And because slicing creates a new object, then the above code effectively copies or clones the whole list into another list.
In short, slicing is a flexible tool to build new lists out of an existing list. Python supports slice notation for any sequential data type like lists, strings, tuples, bytes, bytearrays, and ranges. Also, any new data structure can add its support as well.
Absolutely no difference between them, at least in Python 3. You can check the byte-code produced for each of these using dis.dis
if you'd like:
l = [1, 2, 3, 4]
Byte-code emitted for 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
while, byte-code emitted for 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
as you can see, they're exactly the same. Both load some None
's (the two LOAD_CONSTS
's) for the values of start
and stop
used for building the slice (BUILD_SLICE
) and apply it. None
s are the default for these as stated in the docs for slices
in the Standard Type hierarchy:
Special read-only attributes:
start
is thelower
bound;stop
is the upper bound;step
is thestep
value; each isNone
if omitted. These attributes can have any type.
Use [:]
, it's less key-strokes.
It's actually interesting to note that in Python 2.x
the byte code generated is different and, due to less commands for l[:]
it might be slightly more performant:
>>> 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
While, for 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
Even though I haven't timed these (and I won't, the difference should be tiny) it seems that, due to simply less instructions needed, l[:]
might be slightly better.
This similarity doesn't of course exist only for lists; it applies to all Sequences in 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
similarly for others.
Per the Python language reference section 6.3.2, Subscriptions, the inner expression for a sequence must evaluate to either an integer or a slice. Both these examples produce the same slice, and are therefore identical. There are also numerous other slices that have the same effect, by explicitly stating defaults (start=0
, stop=len(sequence)
or more, step=1
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With