Python: List in global namespace gets unintentionally modified through function - python

I have a propably very basic problem in Python. However, I would be very thankful, if someone could help me to understand, what is happening here:
My code is as follows:
purchaseprices = {'Stock_A': [[10, 4.21],[20, 5.23], [5, 8.32]],
'Stock_B': [[5, 8.23],[15, 7.42], [10, 7.53]]}
def update_purchaseprices(name, number_of_shares):
remaining_number_of_shares = number_of_shares
updated_purchaseprices = purchaseprices[name][:]
indices_to_delete = []
for i in range(len(updated_purchaseprices)):
if updated_purchaseprices[i][0] < remaining_number_of_shares:
remaining_number_of_shares -= updated_purchaseprices[i][0]
indices_to_delete.append(i)
else:
updated_purchaseprices[i][0] = updated_purchaseprices[i][0] - remaining_number_of_shares
break
updated_purchaseprices = [i for j, i in enumerate(updated_purchaseprices) if j not in indices_to_delete]
return updated_purchaseprices
name = "Stock_A"
number_of_shares = 34
print(purchaseprices['Stock_A'])
updated_purchaseprices = update_purchaseprices(name, number_of_shares)
print(updated_purchaseprices) # this works as expected
print(purchaseprices['Stock_A']) # why did the original list got changed as well?
Here is, what I wanted to do: I have an original list, which is stored in a dictionary called purchaseprices. This list can be accessed by purchaseprices['Stock_A’]. Now I tried to write a function to return a list called updated_purchaseprices, which is basically a modified version of the original list. In order to leave the original list unchanged, I made a copy of it by including updated_purchaseprices = purchaseprices[name]:. Unfortunately my code nevertheless also changes the original list. Can someone please tell me why this is happening?

As you probably know because you used [:], a list is mutable and you need to take a copy in your function. But the copy still contains the original objects (sublists).
You need to copy those too!
Replace:
updated_purchaseprices = purchaseprices[name][:]
with:
updated_purchaseprices = [l.copy() for l in purchaseprices[name]]
or:
import copy
updated_purchaseprices = copy.deepcopy(purchaseprices[name])

Related

Having problems with assigning variable to each other [duplicate]

This question already has answers here:
Python: if element in one list, change element in other?
(3 answers)
Closed 2 years ago.
I am pretty new to python and I am trying to swap the values of some variables in my code below:
def MutationPop(LocalBestInd,clmns,VNSdata):
import random
MutPop = []
for i in range(0,VNSdata[1]):
tmpMutPop = LocalBestInd
#generation of random numbers
RandomNums = []
while len(RandomNums) < 2:
r = random.randint(0,clmns-1)
if r not in RandomNums:
RandomNums.append(r)
RandomNums = sorted(RandomNums)
#apply swap to berths
tmpMutPop[0][RandomNums[0]] = LocalBestInd[0][RandomNums[1]]
tmpMutPop[0][RandomNums[1]] = LocalBestInd[0][RandomNums[0]]
#generation of random numbers
RandomNums = []
while len(RandomNums) < 2:
r = random.randint(0,clmns-1)
if r not in RandomNums:
RandomNums.append(r)
RandomNums = sorted(RandomNums)
#apply swap to vessels
tmpMutPop[1][RandomNums[0]] = LocalBestInd[1][RandomNums[1]]
tmpMutPop[1][RandomNums[1]] = LocalBestInd[1][RandomNums[0]]
MutPop.append(tmpMutPop)
Neighborhood = MutPop
return(Neighborhood)
my problem is that I do not want to change the variable "LocalBestInd" and want to use it as a reference to generate new "tmpMutPop"s in the loop, but the code put "LocalBestInd" equal to "tmpMutPop" every time that loop is iterated. The same problem happens for other assignments (e.g., tmpMutPop[1][RandomNums[1]] = LocalBestInd[1][RandomNums[0]]) in this code.
Would you please help me to solve this problem?
Thank you
Masoud
Try this:
import copy
And change the line
tmpMutPop = LocalBestInd
to this:
tmpMutPop = copy.copy(LocalBestInd)
Depending on the structure of LocalBestInd, you may need copy.deepcopy() instead.
Here's a quote from the copy documentation that explains what's going on:
Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other.
Assuming that LocalBestInd is a list, the problem, I think, is that when you're setting
tmpMutPop = LocalBestInd
in the loop, the value tmpMutPop is not a separate list but is just a reference to the list LocalBestInd, which actually contains the data. There's only one list - when you try to update the former, you're really only updating the latter.
Simple example of this works here:
>>> x = [1, 2]; y = x; y[0] = 2; print(x)
[2, 2]
What may help you here is calling .copy() on your list, e.g.:
>>> x = [1, 2]; y = x.copy(); y[0] = 2; print(x)
[1, 2]
If that doesn't work then check out the other list copying methods in this SO answer: How to clone or copy a list?

variable doesn't reset in while loop with function

I have never come across this before and wonder if anyone has a solution. I have a while loop with a function inside that returns an integer. The function takes a string and a list. The first iteration of the while loop returns the correct answer but on subsequent iterations the list seems to be empty even though the function does not change the list in any way. Also if i try to reset the list after the function in the while loop the new list also seems to be empty. It seems to be very strange behavior. Any explanation as to whats going on would be much appreciated. The code of the function inside the loop is quite long so at this stage I will avoid posting it. However if requested then I will post.
spectrum = [1,2,3,4,5,6]
leaderboard = ['zzz','xxx','yyy']
pep_scores = []
j=0
original_spectrum = spectrum
print len(original_spectrum)
while j < len(leaderboard):
x= linear_score(leaderboard[j],spectrum) #this function doesn't alter spectrum
print leaderboard[j], x
spectrum = original_spectrum #should reset spectrum even though it shouldn't be necessary to do that
print len(spectrum), len(original_spectrum) #prints 2 empty lists
pep_scores.append(x) #appends correct score on 1st iteration and '0' for all others
j=j+1
I had added print statements to try to resolve the problem, my original code did not contain 'original_spectrum = spectrum' or 'spectrum = original_spectrum' in the while loop. I don't understand why after 1 iteration 'origninal_spectrum' is an empty list. I haven't posted the function because I cannot see how it could be causing the problem. Please ask if you need more information.
To create copy of the list use copy_list = original_list[:].
So in your example:
spectrum = [1,2,3,4,5,6]
leaderboard = ['zzz','xxx','yyy']
pep_scores = []
j=0
original_spectrum = spectrum[:]
print len(original_spectrum)
while j < len(leaderboard):
x= linear_score(leaderboard[j],spectrum) #this function doesn't alter spectrum
print leaderboard[j], x
spectrum = original_spectrum[:] #should reset spectrum even though it shouldn't be necessary to do that
print len(spectrum), len(original_spectrum) #prints 2 empty lists
pep_scores.append(x) #appends correct score on 1st iteration and '0' for all others
j=j+1
Its because of that you define spectrum outside the function and its scope is global , and when you pass spectrum to your function as its the name of list any changes on it change it globally not local in your function ! and note that its just about mutable (like lists) objects . (Note : labels are pointer to special memory addresses ) (your copy command original_spectrum = spectrum just make 2 label for one object !!! )
for better understanding see the below example :
>>> a=[1,2,3]
>>> def f(s):
... s.remove(1)
...
>>> f(a)
>>> a
[2, 3]
>>> def f(s):
... s+=[1,2]
...
>>> f(a)
>>> a
[2, 3, 1, 2]
Now you have 2 choice :
make a copy of spectrum and pass that to function :
copy_spectrum = spectrum[:]
define spectrum inside the function and one outside for global usage !

Weird list behavior in python

qtd_packs = 2
size_pack = 16
pasta = []
pasta.append ('packs/krun/')
pasta.append ('packs/parting2/')
for k in range(0, qtd_packs):
for n in range(1, size_pack+1):
samples_in.append (pasta[k]+str(n)+'.wav')
samples.append(samples_in)
del samples_in[0:len(samples_in)]
print(samples)
I'm basically trying to add the samples_in inside the samples list, then delete the old samples_in list to create a new one. This will happen 2 times, as the qtd_packs =2. But in the end, what I get is two empty lists:
[[], []]
I've append'ed the samples_in inside samples BEFORE deleting it. So what happened?
Thank you
In Python, lists are passed by reference. When you append samples_in to samples, Python appends a reference to samples_in to samples. If you want to append a copy of samples_in to samples, you can do:
samples.append(samples_in[:])
This effectively creates a new list from all the items in samples_in and passes that new list into samples.append(). So now when you clear the items in samples_in, you're not clearing the items in the list that was appended to samples as well.
Also, note that samples_in[:] is equivalent to samples_in[0:len(samples_in)].
The problem is that after this:
samples.append(samples_in)
The newly-appended value in samples is not a copy of samples_in, it's the exact same value. You can see this from the interactive interpreter:
>>> samples_in = [0]
>>> samples = []
>>> samples.append(samples_in)
>>> samples[-1] is samples_in
True
>>> id(samples[-1]), id(samples_in)
(12345678, 12345678)
Using an interactive visualizer might make it even easier to see what's happening.
So, when you modify the value through one name, like this:
>>> del samples_in[0:len(samples_in)]
The same modification is visible through both names:
>>> samples[-1]
[]
Once you realize that both names refer to the same value, that should be obvious.
As a side note, del samples_in[:] would do the exact same thing as del samples_in[0:len(samples_in)], because those are already the defaults for a slice.
What if you don't want the two names to refer to the same value? Then you have to explicitly make a copy.
The copy module has functions that can make a copy of (almost) anything, but many types have a simpler way to do it. For example, samples_in[:] asks for a new list, which copies the slice from 0 to the end (again, those are the defaults). So, if you'd done this:
>>> samples.append(samples_in[:])
… you would have a new value in samples[-1]. Again, you can test that easily:
>>> samples[-1], samples_in
([0], [0])
>>> samples[-1] == samples_in
True
>>> samples[-1] is samples_in
False
>>> id(samples[-1]), id(samples_in)
23456789, 12345678
And if you change one value, that doesn't affect the other—after all, they're separate values:
>>> del samples_in[:]
>>> samples[-1], samples_in
([0], [])
However, in this case, you really don't even need to make a copy. The only reason you're having a problem is that you're trying to reuse samples_in over and over. There's no reason to do that, and if you just created a new samples_in value each time, the problem wouldn't have come up in the first place. Instead of this:
samples_in = []
for k in range(0, qtd_packs):
for n in range(1, size_pack+1):
samples_in.append (pasta[k]+str(n)+'.wav')
samples.append(samples_in)
del samples_in[0:len(samples_in)]
Do this:
for k in range(0, qtd_packs):
samples_in = []
for n in range(1, size_pack+1):
samples_in.append (pasta[k]+str(n)+'.wav')
samples.append(samples_in)
beetea's answer below offers the solution if you want samples to contain two lists, each of which have the strings for one of your two qtd_packs:
qtd_packs = 2
size_pack = 16
pasta = []
pasta.append ('packs/krun/')
pasta.append ('packs/parting2/')
samples = []
samples_in = []
for k in range(0, qtd_packs):
for n in range(1, size_pack+1):
samples_in.append (pasta[k]+str(n)+'.wav')
samples.append(samples_in[:])
del samples_in[0:len(samples_in)]
print(samples)
produces this output:
[['packs/krun/1.wav', 'packs/krun/2.wav', 'packs/krun/3.wav', 'packs/krun/4.wav',
'packs/krun/5.wav', 'packs/krun/6.wav', 'packs/krun/7.wav', 'packs/krun/8.wav',
'packs/krun/9.wav', 'packs/krun/10.wav', 'packs/krun/11.wav', 'packs/krun/12.wav',
'packs/krun/13.wav', 'packs/krun/14.wav', 'packs/krun/15.wav', 'packs/krun/16.wav'],
['packs/parting2/1.wav', 'packs/parting2/2.wav', 'packs/parting2/3.wav',
'packs/parting2/4.wav', 'packs/parting2/5.wav', 'packs/parting2/6.wav',
'packs/parting2/7.wav', 'packs/parting2/8.wav', 'packs/parting2/9.wav',
'packs/parting2/10.wav', 'packs/parting2/11.wav', 'packs/parting2/12.wav',
'packs/parting2/13.wav', 'packs/parting2/14.wav', 'packs/parting2/15.wav',
'packs/parting2/16.wav']]
Now, when I originally read your question, I thought you were trying to make a single list containing all the strings. In that instance, you could use
samples.extend(samples_in)
instead of
samples.append(samples_in[:])
and you would get a flat list containing only the strings.

A function that changes the value of a list without reason

I'm stunned with this function I've written for python. I've observed that in the lower while it changes the values in the list vctor even though that list is not touched along the function. I've passed the pair (10, [1,3,5,7,10]) and it has changed the list vctor to [1,3,5,8,10]. Is there an explanation for this?
def siguiente(k,vctor):
l = len(vctor)
vctorsig = vctor
i = l-1
while i>= 0:
if vctorsig[i] <= k - l + i:
j=i
while j<=l-1:
print vctor
vctorsig[j] = vctor[i]+j-i+1
j=j+1
i = -1
else:
i = i-1
return vctorsig
When you do vctorsig = vctor, you are making vctorsig a reference to the list referenced by vctor, so when you modify it, you modify the original list.
If you wish to copy the list there, you can simply do vctorsig = list(vctor).
It's a little hard to tell because your indentation is funny, but I think that the line:
vctorsig = vctor
is your problem. In this line, you create a new reference to the same list. In other words, if you change vctorsig (in this line vctorsig[j] = vctor[i]+j-i+1), you also change vctor since they are the same list. The easy way out of this is to make a copy:
vctorsig = vctor[:]

Why does this python code not replace deleted list elements?

Here is my code:
for each in range(0, number_of_trials):
temp_list = source_list
for i in range(10):
x = random.randrange(0, len(temp_list))
board[i] = temp_list[x]
del temp_list[x]
This code is deleting each element from temp_list, as would be expected. But temp_list is not being reset each time the initial for loop runs, setting it back to source_list. As a result, every delete from temp_list is permanent, lasting for every following iteration of the for loop. How can I avoid this and have temp_list "reset" back to its initial status each time?
The statement temp_list = source_list does not create a new list. It gives a new name temp_list to an existing list. It doesn't matter what name you use to access the list—any changes made via one name will be visible via another.
Instead, you need to copy the list, like this:
temp_list = source_list[:]
This creates a new list that starts with the same contents as source_list. Now you can change the new list without affecting the original.
>>> a = [ 1 ]
>>> b = a
>>> del a[0]
>>> print b
[]
Basically, when you use "=", both variables point to the same object. To make a copy, use the copy module.
Copy the list elements instead of the list reference.
temp_list = source_list[:]
This is because :
temp_list = source_list # Doesn't copies the list, but adds the reference.
So, each iteration you are refreshing the reference only.
To copy the list, you can use the trick [:]. This performs list slicing with nothing sliced and produces a new list exactly same as the list being sliced.
Therefore,
for each in range(0, number_of_trials):
temp_list = source_list[:] # Changed
for i in range(10):
x = random.randrange(0, len(temp_list))
board[i] = temp_list[x]
del temp_list[x]
This should work as expected. :)

Categories

Resources