2

I created the class Sorter which sets the variable self.list in the __init__ equal to a given argument. I then created a function selectionSort which should copy the value of self.list into a new variable unsortedList. That worked out, but when I then change unsortedList, the self.list variable changes as well. Here's my code:

class Sorter:
        def __init__(self, list):
                self.list = list

        def selectionSort(self):
                unsortedList = self.list
                sortedList = []
                indexSmallest = 0

                while len(unsortedList)>0:
                        for i in range(len(unsortedList)):
                                if unsortedList[i] <= unsortedList[indexSmallest]:
                                        indexSmallest = i
                        sortedList.append(unsortedList[indexSmallest])
                        unsortedList.pop(indexSmallest)
                        indexSmallest = 0
                return sortedList

sorter = Sorter([2,6,1,8,5])
print(sorter.selectionSort())

I expect self.list to be the same as before calling the selectionSort() function but the result I get is an empty self.list variable.

Marcel Kämper
  • 304
  • 5
  • 16
  • 2
    `unsortedList = self.list` doesn't make a copy, it makes `unsortedList` literally the same list as `self.list`. Any changes to one will happen to the other. – Carcigenicate Jan 10 '19 at 17:47
  • @Carcigenicate What's the name for this interaction? Is it list-only? I know that variables are passed by reference, but I didn't know local assignment happened that way too. – KuboMD Jan 10 '19 at 17:48
  • 1
    @carcigenicate `This is always the case, regardless of type.` - this is not entirely true. Immutable types (int, float, bool, str, tuple, unicode) will be copied. – miga Jan 10 '19 at 17:55
  • @Carcigenicate I see. I've done a lot of programming in Python and never run into that - I can see why though, it's definitely never a smart thing to do. – KuboMD Jan 10 '19 at 17:56
  • I concur with miga. @Carcigenicate, consider the following: `a = 5; b = a; a = 0`, `b` will still be `= 5`. This is different if its a `list`, `a = [5]; b = a; a.append(6)` `b` will now be `= [5,6]` – ycx Jan 10 '19 at 17:57
  • @marcel-kämper - you can see what is going on with your code in move visual way here - http://www.pythontutor.com/visualize.html#mode=edit – miga Jan 10 '19 at 17:57
  • @miga Really? Why would an immutable object be copied? That's the one type of object that shouldn't need to be copied in most scenarios. – Carcigenicate Jan 10 '19 at 17:58
  • @Carcigenicate see above example – ycx Jan 10 '19 at 18:00
  • @ycx That doesn't show copying unless I'm missing something. `a` points to 5, then `b` points to the 5, then `a` points to 0. I don't see why copying would come into play there. – Carcigenicate Jan 10 '19 at 18:00
  • a is pointing to 0 now, while b is pointing to 5 still. If this was the same object in memory, then something is wrong. b clearly copied the immutable object – ycx Jan 10 '19 at 18:01
  • actually it's more interesting than this - [https://medium.com/broken-window/many-names-one-memory-address-122f78734cb6](Assignment statements in Python are more interesting than you might think) – miga Jan 10 '19 at 18:08
  • @ycx This may be a misunderstanding on my part about how Python handles variables, but if `a` points to the memory address of 5, then `b` is assigned and points to the same address as `a`, reassigning `a` shouldn't effect anything, since that's just changing where `a` is pointing. That doesn't effect what `b` is looking at. And your list example is entirely different. You have both variables point to the same object, then *mutate* the object. That's not showing a difference between reassignment in primitives vs non-primitives, that's showing mutation vs assignment. – Carcigenicate Jan 10 '19 at 18:10
  • @miga That link is broken. I would be interested in learning more though, as apparently I have a gap in my knowledge. (Nvm, fixed it. You had some stuff on the end.) – Carcigenicate Jan 10 '19 at 18:11
  • Sorry - https://medium.com/broken-window/many-names-one-memory-address-122f78734cb6 – miga Jan 10 '19 at 18:19
  • @KuboMD this is just the normal semantics of assignment in Python. The type of object involved doesn't matter, and *all types of assignment* work this way. Read https://nedbatchelder.com/text/names.html – juanpa.arrivillaga Jan 10 '19 at 18:33
  • @miga absolutely not true. The *type of the object* does not matter. Immutable types are never copied on assignment. *No object is ever copied on assignment*. – juanpa.arrivillaga Jan 10 '19 at 18:33
  • @ycx that isn't a valid comparison. Consider `a = [1,2]; b = a; a = ['a','b']` then `b` will be `[1,2]`. `.append` is a *mutation*. int objects are *immutable*. Assignment *does not mutate*. But *assignment* works the same way regardless of type – juanpa.arrivillaga Jan 10 '19 at 18:34
  • @juanpa.arrivillaga I got flak for claiming that in a comment I deleted. I have been unable to replicate assignment causing copying of primitives though, so I'm not sure if it actually happens as claimed. – Carcigenicate Jan 10 '19 at 18:34
  • @Carcigenicate because it does not. That is a common misconception, there is *absolutely no difference between the semantics of assignment in Python based on the types involved*. – juanpa.arrivillaga Jan 10 '19 at 18:36
  • @juanpa.arrivillaga God, thank you. Here I was thinking I had somehow had that wrong for a few years. Been sweating bullets here for like half an hour. – Carcigenicate Jan 10 '19 at 18:36
  • That link is talking about the special case of *string literals*, which the *compiler* optimizes (sometimes) to make the same objects. But that doesn't change the semantics of assignment, it is just an implementation detail about string literals. – juanpa.arrivillaga Jan 10 '19 at 18:38
  • 1
    @juanpa.arrivillaga you are correct. the new object is created when you try to change the immutable one. my mistake. – miga Jan 10 '19 at 18:41
  • 1
    @miga Yes, it is important to grok that you *simply cannot even try to change* an immutable object. All immutable means is "the object's interface exposes no mutator methods". But *assignment* is not a method of the objects interface. – juanpa.arrivillaga Jan 10 '19 at 18:43

2 Answers2

2

Use either:

#1
unsortedList = self.list.copy()

Or

#2
unsortedList = self.list[:]

Or

#3
import copy
unsortedList = copy.deepcopy(self.list)

Explanation:

When you do an assignment via =, it really is referring to the same list just that now that list has 2 different names.

To circumvent this, use #1 or #2 methods -> you would require the .copy() inbuilt function or using [:].

As for #3, this is used when shallow copying isn't enough because you might have mutable objects within the list itself.
For a greater understanding on copy vs deepcopy, visit and read here

ycx
  • 3,155
  • 3
  • 14
  • 26
1

What's happening is that when you set unsortedList = self.list, Python doesn't want to copy all the values over, because that could be expensive. Instead, it just makes both variables point to the same region of memory, so when you change one it changes the other.

To make a copy, you can do unsortedList = self.list[:]

EDIT see this thread for more information.

Calvin Godfrey
  • 2,171
  • 1
  • 11
  • 27