Does the list slicing produce a new list?

I’m a newbie of python, and very confused about the list slicing.

For example,

from typing import List


def buddle_sort(lst: List):
    if len(lst) < 2:
        return
    i = 0
    while i < len(lst) - 1:
        if lst[i+1] < lst[i]:
            lst[i], lst[i+1] = lst[i+1], lst[i]
        i += 1
    buddle_sort(lst[:len(lst)-1])


a = [100, 8, 3, 4, 6]
buddle_sort(a)
print(a)

a[len(a):] = [1, 2]
print(a)

On the first hand, in the recursive buddle sort, I pass the sliced sub-list for each recursion, but obviously, it doesn’t work. It seems that slicing would create a new list and copy corresponding elements from the original list.
But, on the other hand, a[len(a):] = [1, 2] would change the original list, why?

Hello, @kingluo. No, slicing returns a list which is inside the original list. Not a copied list. But, in your code; there is a function which edits the parameter-list. If you are working with parameters, you must know that changes made at a parameter doesn’t effect the original variable you pass into it. To understand this better, you can try running this basic code:

def abc(number):
    number -= 5 #this change is made on the parameter, not on the original variable.

myNumber = 10
abc(myNumber)
print(myNumber)#prints 10, NOT 5.

To change your original variable, you can use the return keyword. In your code, I can see that you have also used a return but it actually returns None. Because, its normal syntax is:

return something

Let’s try working with it on our example code:

def abc(number):
    number -= 5 #this change is made on the parameter, not on the original variable.
    return number

myNumber = 10
abc(myNumber)
print(myNumber)#still printing 10.

This code still prints 10, because return keyword just assigns the value of our function; not automatically updates our original variable. To succeed that; you should pass (assign) that value to your original variable:

def abc(number):
    number -= 5 #this change is made on the parameter, not on the original variable.
    return number

myNumber = 10
myNumber = abc(myNumber) #assigning the value, which our function returns, to our original variable.
print(myNumber)#prints 5!

Right now, we can apply the same changes to your code, like this:

from typing import List


def buddle_sort(lst: List):
    if len(lst) < 2:
        return lst
    i = 0
    while i < len(lst) - 1:
        if lst[i+1] < lst[i]:
            lst[i], lst[i+1] = lst[i+1], lst[i]
        i += 1
    buddle_sort(lst[:len(lst)-1])


a = [100, 8, 3, 4, 6]
a = buddle_sort(a)
print(a)

a[len(a):] = [1, 2]
print(a)

But; this code will also not work properly, it will just print None and give an error like, ‘you can not use len() on NoneType object’. Because, you have a recursive function. And, that kind of functions behave a bit more different than what you expect. You can see this post about recursive functions.(please see it before reading the rest of my post).
So, it’s hard to work with recursive functions. To work with them, you need to use an external variable, not a parameter. You can use the global keyword with a global variable but that probably won’t be pretty. You can make it more useful and easier with two different sort functions- one is the process manager (main) function, and one is the process itself.

from typing import List

def _buddle_sort(lst: List):#process
    i = 0
    while i < len(lst) - 1:
        if lst[i+1] < lst[i]:
            lst[i], lst[i+1] = lst[i+1], lst[i]
        i += 1
    return lst[:-1] # this is the same slice with lst[:len(lst)-1]

def buddle_sort(lst: List): #process-manager main buddle_sort method.
	while True:
		lst = _buddle_sort(lst)
		if len(lst)<2:
			return lst

a = [100, 8, 3, 4, 6]
a = buddle_sort(a)
print(a)

a[len(a):] = [1, 2]
print(a)

[EDIT:I have just realised that doing just this could also make the code properly worked:

from typing import List


def buddle_sort(lst: List):
    if len(lst) < 2:
        return lst#changed
    i = 0
    while i < len(lst) - 1:
        if lst[i+1] < lst[i]:
            lst[i], lst[i+1] = lst[i+1], lst[i]
        i += 1
    return buddle_sort(lst[:len(lst)-1])#changed to prevent returning None when if block isn't executed 


a = [100, 8, 3, 4, 6]
a = buddle_sort(a)#changed
print(a)

a[len(a):] = [1, 2]
print(a)

]

I hope, this respond will help you.

Thanks for your detailed reply.

I still have some questions.

The argument in list type is passed as a reference in python, isn’t it? If so, the recursion should always modify the original list.

While the number in your example is in number type, it should make a copy just like other programming languages? Is it?

I think, typing.List doesn’t do anything in practice. It just says user to pass an argument in list type.(This is what I know.)

And, in Python, two snippets below are the same:

a = [1,2,3]
func(a)
func([1,2,3])

So,I think, we can say that Python creates a copy of the variable’s value and passes it into the function as an argument.
But in classes and object-based methods, you can change the object and its attributes directly.

Maybe this is the answer to my original question?
The slicing will create a new list (I check that the id() is different), but copy the references of the corresponding elements of the original list to the new list. When I do the assignment to the element of the new list, it does not affect the original list, because it just overrides the element reference of the new list. So that’s why my recursive buddle sort failed.
But when the whole list slice is at the left side of an assignment, the right value is assigned to it in place, so it’s special. As well as del lst[2:].

I change my code as below:

def buddle_sort(lst: List):
    if len(lst) < 2:
        return
    i = 0
    while i < len(lst) - 1:
        if lst[i+1] < lst[i]:
            lst[i], lst[i+1] = lst[i+1], lst[i]
        i += 1
    tmp = lst[:-1]
    buddle_sort(tmp)
    lst[:-1] = tmp

And it works now.

Okay. When I was saying “slicing returns a list which is inside the original list. Not a copied list”, I have meant that slice is inside the list, and any changes made on that slice will effect the main list. But yes, it’s a list alone. I thought that you thought it is a totally different list. Sorry for confusing sentence and misunderstanding.
And, when we come to your code; It seems I couldn’t understand well what you exactly wanted to do. -Actually, if block a bit confused me and recursive function was sending the list without its last value to itself as an argument. I am very surprised now how i couldn’t understand it from your function’s name.- Sorry for these, really.
About your main question, it seems, I gave you some classic methods-but code was wrong because I understood wrong what you exactly wanted to do with it.- and your question wasn’t about it at all. Also sorry for this.

Yes, it seems you are right. It may be a kind of thing called slice assignment. BTW, you can see this link about your question.

Thank you for your detailed reply again.

1 Like
def mysort(L: list[int]) -> None:
    for i in range(len(L)):
        for j in range(i + 1, len(L)):
            if L[i] > L[j]:
                L[i], L[j] = L[j], L[i]

if __name__ == '__main__':
    L: list[int] = [3,2,1]; print(L)
    mysort(L); print(L)
    L = [100, 8, 3, 4, 6]; print(L)
    mysort(L); print(L)

"""
$ mypy appdir/main.py 
Success: no issues found in 1 source file
$ python3 appdir/main.py
[3, 2, 1]
[1, 2, 3]
[100, 8, 3, 4, 6]
[3, 4, 6, 8, 100]
$
"""

Yes, slicing returns a new list. We only need simple code to show that:

alist = [1, 2, 3, 4, 5, 6, 7]
blist = alist[1:4]  # Get a slice taken from alist.

You can now inspect blist and see it is a list:

type(blist)  # returns <class 'list'>

You can modify blist and see that alist does not change:

blist[0] = 999
print(alist)

So the slice is a new list.

Slice assignment assigns into the list you are assigning to. It
works just like item assignment:

alist[0] = 111  # Assign to element 0 of alist.
print(alist)

# Assign to the slice 1:4, which is elements (1, 2, 3)
alist[1:4] = [22, 33, 44]
print(alist)

The tutorial covers slicing with lists:

https://docs.python.org/3/tutorial/introduction.html#lists

You can read more about slicing in the reference manual:

https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range

Toprak Aslan said:

“slicing returns a list which is inside the original list. Not a copied list.”

That is incorrect. Slicing of lists (and tuples, strings and other built-ins) returns a copy of the sublist.

You can make a copy of the entire list by leaving the slice indexes blank, which default to the start and end of the list:

alist = [1, 2, 3, 4]
blist = alist[:]  # slice defaults to 0:len(alist)

That makes blist a copy of alist:

alist == blist  # returns True

# Change the copy and the original does NOT change.
blist.append(999)
print(alist)  # prints [1, 2, 3, 4]

Slices don’t have to copy the entire list:

alist[1:3]  # returns a new list by copying elements (1, 2).

Remember that in Python, slice indexes are “half-open” intervals. The start index is included, and the end index is not.

By Toprak Aslan via Discussions on Python.org at 27Mar2022 15:22:

And, in Python, two snippets below are the same:

a = [1,2,3]
func(a)
func([1,2,3])

So,I think, we can say that Python creates a copy of the variable’s value and passes it into the function as an argument.

Well, in that the variable always holds only a reference to an object,
yes the reference is passedcopied. The object itself is not copied.

But in classes and object-based methods, you can change the object and
its attributes directly.

There’s nothing special there.

def f(x):
    x.a = 2

class C:
    def m(self):
        self.a = 3


o = C()
o.a = 1
print(o.a)
f(o)
print(o.a)
o.m()
print(o.a)

should print:

1
2
3

The variable o is a reference to the instance of C we made. The
function f() gets a copy of that reference as the parameter x. So
modifying x.a modifies the .a attribute of that object, and when we
access it later via o.a we see that modification.

The only “special” thing with classes and methods is that calling
o.m() implicitly provided a reference to o as the self parameter
of the m() method.

However, if we continued the code example above:

def f2(x):
    x = C()
    x.a = 9
    print(x.a)

print(o.a)
f2(o)
print(o.a)

it should print:

3
9
3

This is because f2’s parameter x gets a copy of the reference from
o - a reference to the orignal instance, just as f() did. But then
we replace that reference with a reference to a new instance of C,
then modify the .a attribute of the new instance. So printing x.a
prints 9. But after the return, we have not changed what o refers
to, which is the original instance, and its attribute o.a is still
3.

Cheers,
Cameron Simpson cs@cskk.id.au

By jinhua luo via Discussions on Python.org at 27Mar2022 15:54:

Maybe this is the answer to my original question?
The slicing will create a new list (I check that the id() is different), but copy the references of the corresponding elements of the original list to the new list. When I do the assignment to the element of the new list, it does not affect the original list, because it just overrides the element reference of the new list. So that’s why my recursive buddle sort failed.

Yes A list is just a list of references to objects (in your case, some
integers). A slice of a list gets you a new list, containing the
reeferences selected by the slice.

But when the whole list slice is at the left side of an assignment, the
right value is assigned to it in place, so it’s special. As well as
del lst[2:].

Yes. These are not copies of the list, they are a specification of some
range of elements in the list. So:

L[2:4] = [7,8,9]

is a specification of the elements with indices 2 and 3, to be replaced
with the elements from the new list on the right hand side of the
assignment:

>>> L=[1,2,3,4,5]
>>> L[2:4]=[7,8,9]
>>> L
[1, 2, 7, 8, 9, 5]

Likewise with del, it is a specification of what to delete from the
list, like:

>>> del L[2:4]
>>> L
[1, 2, 9, 5]

If I undo that and use an assignment:

>>> L = [1, 2, 7, 8, 9, 5]
>>> L[2:4] = []
>>> L
[1, 2, 9, 5]

we get the same result, replacing elements with indices 2 and 3 with the
elements from an empty list.

Under the covers, these operations are mediated by special methods on
the list class.

If you want to implement slicing in a class of your own, you can
implement these methods: __getitem__, __setitem__, __delitem__.
You don’t even need to implement all of them!

So if you have some object L then this:

L[2:4]

in an expression returns the result from this function call:

L.__getitem__( slice(2,4) )

“slice()” is a built in function to make a “slice” object:

>>> slice(2,4)
slice(2, 4, None)

so you can play with that directly. When you do:

L[2:4] = []

that calls:

L.__setitem__( slice(2,4), [] )

and:

del L[2:4]

calls:

L.__delitem__( slice(2,4) )

so what happens is actually up to the class definition. For a list
they do what you expect of a list. By contrast, the expression:

L[2]

is the result of this function call:

L.__getitem__(2)

So like you might hope, all indexing goes through __getitem__. It is
just that with L[2:4] that function is passed a “slice” object instead
of an integer. The function has to decide what to do with it.

Why this digression? Because __getitem__ DOES NOT need to make a new
list (or new “thing”, whatever class it is). Some classes might do
something else, meaningful for that class. For lists, it does make a
new list.

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like