Python : Modifying Lists in For Loops [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 6 years ago.
I am trying to create card game simulations to improve my programming skills a bit, and i am stuck with the following problem:
I have the cards in a big list, and i use the next function to pick a card and remove it from the deck.
cards= [........(big list of cards here in string format).......]
def Pickacard(x):
rand=random.randint(0,len(x)-1)
t=int(x[rand])
del(x[rand])
return t
When i use the following iteration, the deck isnt beeing renewed. Instead every time a card gets picked, the deck remains 1 item shorter, despite the fact that in every loop, i have set "test=cards" so that it sets the list back to the original.
for i in range(200):
test=cards
Pickacard(test)
print(test)
Deck had 208 cards, if the deck was renewed then (print test) would give me a list of 207 cards. Instead i get a list of 8 cards. I though this would only happen if ....test=cards.... was outside of the loop.

When calling test = cards you are not copying the list cards in test. In fact, what you are doing is merely copying the pointer test into cards. So they are pointing on the same list. This is why the list keeps shrinking and is never renewed.
If you want to renew it, you have several possibilities. You could created a copy of the list with the following:
Slice it with test = cards[:] (not the best way of doing that)
Create it with list(): test = list(copy)
Explicit copy : test = copy.copy(cards) (must import the module copy first)
Else, you can save the card you deleted and reinsert it at the end of the loop. This avoids copying the list at each iteration. Though it's not crucial in your case, it's a good practice to think a bit about performances by avoiding unnecessary computing:
def Pickacard(x):
rand=random.randint(0,len(x)-1)
card = x[rand]
del(x[rand])
return card
for i in range(200):
card = Pickacard(cards)
print(cards)
cards.append(card)

You're copying the reference, not the list data. Try the following options.
import copy
test = copy.copy(cards)
OR
test = list(cards)
Also, read about Deep and Shallow Copy

In Python, variable is just an alias of an object. so in your program, test and cards are two alias of the same object. So when you delete an item in cards, test changes too.
If you want test to be another object, you can use
test = cards[:]
This will build a new List that contains all the item of cards.
You can see more about how to copy a List here
How to clone or copy a list in Python?

Related

Python shallow copy and deep copy in using append method

Some problems come out when using append method in python3.5. The code is presented
# generate boson basis in lexicographic order
def boson_basis(L,N):
basis=[]
state=[0 for i in range(1,L+1)]
pos=0
# initialize the state to |N,0,...,0>
state[0]=N
basis.append(state)
# find the first non-zero position in reverse order
while state[L-1]<N:
for i in range(-2,-L-1,-1):
if state[i]>0:
pos=L+i
break
sum=0
for i in range(0,pos):
sum=sum+state[i]
state[pos]=state[pos]-1
state[pos+1]=N-sum-state[pos]
basis.append(state)
return basis
result=boson_basis(3,3)
the expected result should be [[3,0,0],[2,1,0],...,[0,0,3]], but this code generates wrong results with all elements are the same as the last one, i.e. [[0,0,3],...,[0,0,3]]. I use the pdb to debug it and I find that once the state is modified, the former state that has been appended into basis is also changed simultaneously. It implies that append uses deepcopy automatically which is beyond my understanding. In fact, this error can be fixed if we use basis(state.copy()) explicitly.
On the other hand, the following simple code shows no error in using append
x=3
b=[]
b.append(x)
x=x+2
after x is changed to x=5, b remains unchanged b=[3]. It really puzzles me and seems contradictory with the former example.
As revealed in the comments already, there's no copy whatsoever involved in an append operation.
So you'll have to explicitly take care of this yourself, e.g. by replacing
basis.append(state)
with
basis.append(state[:])
The slicing operation with : creates a copy of state.
Mind: it does not copy the lists elements - which as long as you're keeping only plain numbers and not objects in your list should be fine though.

Python make list copy, not reference [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 8 years ago.
I have list of int's called board in python code. I wan't to know, if it was modified, so I have code
self.oldboard = list(self.board)
#here is board modified, if it is possible
if not self.oldboard == self.board:
#this should execute only when board was modified
But oldboard is always equals to board, when I modify board, it modifies oldboard. How to make oldboard just copy of board, not reference?
When copying lists by the slice method (analogous to what you're currently doing):
new_list_copy = old_list[:]
you'll only get a "shallow" copy of the contents. This isn't suitable for lists that contain lists ("nested lists").
If you're trying to copy a nested list, a Pythonic solution is to use deepcopy from the copy module:
import copy
new_list_copy = copy.deepcopy(old_list)
Integers are immutable. I would recommend you familiarize yourself with the notions of shallow and deep copy operations, which you can find in the Python Docs here . In your case you most likely need to use deepcopy, as I would guess that you have several nested lists.

Python list in list undo

I'm making a program that will do some operations like add, insert, pop on a list of numbers.
I have to also make an "undo" function and for that I thought about making a list in list to save up my list every step of the way but my tests are not working. Here's the code:
def backup(l):
lbackup.append(list(l))
<-- here I save the list every step of the way by calling this function
> def undo(l):
> l=lbackup[len(lbackup)-1] # here I give my list the previous list
> del lbackup[len(lbackup)-1] # here I delete the sublist from the history
My test:
def testUndo():
add(lista, 1)
backup(lista)
add(lista, 2)
backup(lista)
add(lista,111111)
print(lbackup)
print(lista)
undo(lista)
print(lista)
print(lbackup)
So I checked the inside of the Undo function and it's working! It actually changed the value of my list to what I wanted but once it left the function and got to the print after undo in the test... it had the same values again. I know lists are mutable and that might be a problem but is there anything I could do? Sorry if I wasnt clean enough.
When you call, say, undo(lista), the l inside of undo may be the same list as lista, but assigning to l does not change lista. If instead you had undo return l, and assign the value returned by undo back to list, that would make the change you seem to want.

Python Lists append mutable variable

Still new to programming/scripting, and this one's been bothering me. I have a function that searches through a list of names, comparing it to a template list of names, and when it finds a match, it places it in my final list in the correct order. For some later functions to work correctly, I need to be able to append some of these names as arrays/lists with. I'm running into the problem that every time I need to add a list to the final list, as soon as I change the variable, the final list updates with it. How do I fix this?
light = ['template of names in here in correct order']
listUser = ['names gathered from user input']
for userChan in listUser:
for channelName in light:
#check if channelName is a list or string
if isinstance(channelName, basestring):
#search for matches in userchan
print channelName, 'is a string'
if channelName in userChan.lower():
matchFound = True
listLight.append(userChan)
else:
print channelName, 'is a list'
for piece in channelName:
print 'searching %s in %s' %(piece, userChan.lower())
if piece in userChan.lower():
print "found %s in %s" %(piece, userChan.lower())
lightMultList.append(piece)
matchFound = True
if len(lightMultList) == 2:
listLight.append(lightMultList)
del lightMultList[:]
So my problem is with the lightMultList. It's always going to be limited to 2 elements, but it changes. Hopefully this wasn't worded too horribly..
The problem is that you're only ever creating one lightMultList. You repeatedly clear it out (with del lightMultList[:]) and re-fill it, and append the same thing over and over to lightList.
The simple fix is to just create a new lightMultList each time. Which you can do by changing this line:
del lightMultList[:]
… to:
lightMultList = []
This kind of problem is often a result of trying to directly porting C or C++ code, or just thinking in C++. If you were expecting lightList.append(lightMultList) to call a "copy constructor", that's the root problem: there is no such thing in Python. Assigning a value to a variable, appending it to a list, etc., doesn't copy anything; it just binds another reference to the same value.
Also, a C++ programmer might try to optimize performance by avoiding the wasteful creation of all those temporary objects by trying to reuse the same one, but in Python, the cost of creating a new list is about the same as the cost of iterating one step over listUser in the first place. If it's slow enough to worry about, you're going to have to reorganize your code or move the whole thing to C or Cython anyway; this isn't going to help. (That being said, it's rarely a useful optimization in C++ either; the right thing to do there, on the rare occasions where it matters, is to construct the new vector in-place within the containing vector…)

Appending instances of an object to a list only works with time consuming deepcopy, how can I change this?

I have a pymzml.run.Reader class from the pymzml package. This is a generator object, when looping through it it yields instances of the Spectrum class (also from the pymzml package). I'm comparing different instances with each other. Because pymzml.run.Reader is a generator object, after looping through them they can't be used anymore, so I save them in a list for comparison later on.
However, when I save them in a list and then loop through the list printing the id's of the spectra, it shows that it only save the last spectrum. To clarify:
import pymzml
def test(msrun):
for spectrum in msrun:
print spectrum['id']
spectrumList.append(spectrum)
print '-'*20
for i in spectrumList:
print i['id']
msrun = pymzml.run.Reader(r'JG_Ti02-C1-1_C2-01A_file1.aligned.mzML')
gives:
1
2
3
4
5
--------------------
5
5
5
5
5
The pymzml has a deRef() function that makes a deepcopy of the spectrum, so the following does work correctly:
import pymzml
def test(msrun):
for spectrum in msrun:
print spectrum['id']
spectrumList.append(spectrum.deRef())
msrun = pymzml.run.Reader(r'JG_Ti02-C1-1_C2-01A_file1.aligned.mzML')
However, making deepcopies is a major bottleneck which I'm trying to get out of my application. How can I append the spectrum instances to a list so that not only the last spectrum is appended multiple times?
It can't be just saving the last spectrum -- you're doing all the right things to save each object to the list.
The problem is you're getting the same object over and over.
Printing id(spectrum) in the loop to get its memory address will show that it is one object repeated with its id and other attributes changed.
While you don't necessarily need copy.deepcopy(), you do need to make a copy. Try copy.copy(), and look at the source of Spectrum.decRef() to see how it does its copying.
Most likely, you do need to decRef() each one to make them independent -- otherwise, why would the class provide a special method?

Categories

Resources