Self variable changes when the variable it's assigned to changes [duplicate] - python

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 4 years ago.
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.

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

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.

Related

Why are attributes in different objects connected to each other when a default argument is given? [duplicate]

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 3 years ago.
I am implementing a basic node object in python. Basically, I implemented a node class with the attribute f_pointers and set it to the default value []. When ever I try to change f_pointers of (lets say) node_a, I will end up changing f_pointers of node_b, which are programmed to be completely unrelated.
I have already solved the problem by instead changing the default value to None and setting up the forward_pointers in __init__. However, I would still like to know how to avoid this problem in the future and possibly learn something new about Python.
For the sake of simplicity, I removed some unnecessary parts of the code.
class Node:
def __init__(self, f_pointers = []):
self.f_pointers = f_pointers
def get_pointers(self):
return self.f_pointers
def add_pointers(self, new_pointer):
self.f_pointers.append(new_pointer)
a = Node()
b = Node()
print(a.get_pointers, b.get_pointers)
>>> [] []
a.add_pointers("a")
print(a.get_pointers, b.get_pointers)
>> ["a"] ["a"]
a.add_pointers("b")
print(a.get_pointers, b.get_pointers)
>> ["a","b"] ["a","b"]
As can be seen, a and b are completely unrelated objects (other than the fact that they are of the same type Node) but will affect each other. Why does this happen?
It's because you are referencing to the same list (the one instantiated in the __init__ default params list definition like __init__(self, f_pointers=[]). What happens is that when you say in the __init__ method code block that self.f_points = f_pointers you are basically referencing the same list every time you instantiate a new Node object.
The reasons are explained further here
What you do want to do instead is instantiate a new list for every init like:
def __init__(self, f_pointers=None):
self.f_pointers = []
You should do it like this.
class Node:
def __init__(self, f_pointers=None):
if f_pointers:
self.f_pointers = f_pointers
else:
self.f_pointers = []
def get_pointers(self):
return self.f_pointers
def add_pointers(self, new_pointer):
self.f_pointers.append(new_pointer)
a = Node()
b = Node()
print(a.get_pointers(), b.get_pointers())
a.add_pointers("a")
print(a.get_pointers(), b.get_pointers())
You get this kind of behavior because in your case a.f_pointers and b.f_pointers is the same list, which was generated, when you described your class Node.
So a.f_pointers is b.f_pointers == True in your case

Pass a list to a class python [duplicate]

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 9 years ago.
I have this simple class:
class revs:
def __init__(self, rev, us, accs = []):
self.rev = rev
self.us = us
self.accs = accs
And i have this piece of code to asign values to the list and is inside of a loop
rev, usu = cada_l.split("|")
acct = each_l[:2].strip()
list_acct.append(acct)
and last, i create a dict, to manage a list of revs like this:
drevs = {}
cada = revs(rev, us, list_acct)
drevs[cada.rev] = cada
And it Works correctly with rev and us, but with list_acct is ever updating all the instances:
drevs['1'].rev
'1'
drevs['2'].rev
'2'
drevs['1'].us
'user1'
drevs['2'].us
'user2'
drevs['1'].accs
'["Doc1","Doc2"]'
drevs['2'].accs
'["Doc1","Doc2"]'
And if i change list_acct.clear(), the values in all the instances is clear, I'm still fairly new to Python and this confuses me.
Thanks
This looks like it's happening because you're passing the same list to every object. As a result, all the objects maintain references to the same list, and since list is mutable, it appears to change "all" of them at once.
To fix this, either pass in a new empty list each time you create a revs object, or else clone the list you're passing in:
cada = revs(rev, us, list_acct[:])
Note that if list_acct contains mutable objects, you could still get into the same problem again, but one level deeper!
If you're not passing lists to the revs objects when you create them at all (I can't tell, since you're not showing your full code!), then you have the same problem, but for a different reason: in Python, default arguments are all evaluated once, at the time of the function definition. Therefore, you can get this behavior:
r1 = revs(1, 1)
r2 = revs(2, 2)
r1.accs.append("Hi!")
print(r1.accs) # prints ['Hi!']
print(r2.accs) # prints ['Hi!']
Because the default argument for the revs constructor is always pointing to the same list. See this question for an explanation as to why, but to get around it, just use None as your default instead of [].
class revs:
def __init__(self, rev, us, accs=None):
self.rev = rev
self.us = us
if accs is None:
accs = []
self.accs = accs

deep copy in python [duplicate]

This question already has answers here:
How to avoid having class data shared among instances?
(7 answers)
Closed 9 years ago.
I was working with Python deep copy trying to create a total copy of original object, but the deep copy didn't seem to create a copy. It still shares the same reference with original object, which is not desired
Here's the code. I have a class Board, the instance of which I want to deep copy.
import copy
class Board:
xpos = None
opos = None
count = None
status = []
def __init__(self, size):
self.xpos=[0,0]
self.opos=[size-1,size-1]
self.count = size*size-2
for i in range(size):
tmp = ['-']*size
self.status.append(tmp)
self.status[0][0] = 'X'
self.status[size-1][size-1]= 'O'
Somewhere in another function I want to call
board=Board()
localboard=copy.deepcopy(board)
# then do modification to local board....
# but it seems the old board is also affected. This is so weird since
# I am already using deep copy.
So how can I create a deep copy of the old board? I don't want to share any reference, since I will do modification on the local one and want to keep the old intact..
You should remove the following from the Board definition:
xpos = None
opos = None
count = None
status = []
and add the following to its __init__():
self.status = []
Otherwise all your instances share the same status, which is an attribute of the class, not of the instance.

What does list[:] = process_list(list) does in python? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What is the difference between list and list[:] in python?
I am quite new in python so I bumped into a situation where I was unable to find a response for the following question.
What does this means in python?
l[:] = process_list(l)
l is type list
Basically I have a global declared list that I want to modify it(override the old values with the new ones) based on the response of the process_list method. When I try like this:
l = process_list(l)
I get this: Unresolved reference 'l'
Can you please explain what is the difference and if the first approach that I am using currently is a good one?
In a function, an assignment to a name that could be a local variable creates a local variable by that name, even if it shadows a global:
a = None
def foo():
a = 5 # local 'a' shadows global 'a'
Slice assignment is modification, not assignment, so the name continues to refer to the global:
a = [1, 2, 3]
def foo():
a[:] = [5] # modifies global 'a'
The Unresolved reference happens because by creating a local variable shadowing the global, the global can no longer be seen. Another way to do what you want could be to use global:
a = None
def foo():
global a
a = 5 # rebinds global 'a'
list[:] = whatever will change the contents of the existing list
(as opposed to replacing it with list = whatever) ... by the way list is a terrible variable name ...

List in a Python class shares the same object over 2 different instances? [duplicate]

This question already has answers here:
How to avoid having class data shared among instances?
(7 answers)
Closed 15 days ago.
I created a class:
class A:
aList = []
now I have function that instantiate this class and add items into the aList.
note: there are 2 items
for item in items:
a = A();
a.aList.append(item);
I find that the first A and the second A object has the same number of items in their aList.
I would expect that the first A object will have the first item in its list and the second A object will have the second item in its aList.
Can anyone explain how this happens ?
PS:
I manage to solve this problem by moving the aList inside a constructor :
def __init__(self):
self.aList = [];
but I am still curious about this behavior
You have defined the list as a class attribute.
Class attributes are shared by all instances of your class.
When you define the list in __init__ as self.aList, then the list is an attribute of your instance (self) and then everything works as you expected.
You are confusing class and object variables.
If you want objects:
class A(object):
def __init__(self):
self.aList = []
in your example aList is a class variable, you can compare it with using the 'static' keyword in other languages. The class variable of course is shared over all instances.
This happened because list is a mutable object, and it is created once only when defining the class, that is why it becomes shared when you create two instances. Eg,
class A:
a = 0 #immutable
b = [0] #mutable
a = A()
a.a = 1
a.b[0] = 1
b = A()
print b.a #print 0
print b.b[0] #print 1, affected by object "a"
Therefore, to solve the problem, we can use constructor like what you have mentioned. When we put the list in constructor, whenever the object is instantiated, the new list will also be created.
In Python, variables declared inside the class definition, instead of inside a method, are class or static variables. You may be interested in taking a look at this answer to another question.

Categories

Resources