Python and assignment [duplicate] - python

This question already has answers here:
List assignment to other list
(3 answers)
Closed 5 years ago.
I'm trying to understand (on the level of principle) the difference between assignment between (say) integers variables and list variables.
Integer case:
a=6
b=a
print(b) #prints 6
a=7
print(b) #prints 6
That makes sense to me with this logic: in the original b=a, b was given the same value as a (6), not identified with a. So if I change the value of a, the value of b does not change: b is not the same thing as a.
List case:
L=[1,2]
M = L
print(M)
L.append(6)
print(M)
This can make sense with this logic: in M=L I'm forcing M to literally be the same object as L is, I'm identifying it with L. So if L changes, so does M.
What doesn't make sense (to me) is why I need different logic in different cases. (Probably this has to do with integers being "immutable" and lists "mutable" but I don't see how that bears on this.) Can someone point me to a explanation? What is the principle behind the difference in behaviors? (I'm not looking so much for how the technical difference in implementation of integers and lists leads to the difference, but for the reason things were designed this way. What's the logic?)

Every name is a variable holding a reference to some object.
What happens in the first case is that
a = 6
b = a # (a and b point to the same object)
But here, you are changing what a points to:
a = 7
Compare this to the second/list situation, where you actually call the method on the first object. You didn't update the reference as you did in the case with the integers.
L = [1,2]
M = L # here you introduce a new name, referencing the same object as L.
L.append(6) # update that same object.
print(M) # you print that same object

You don't have different logic in different cases here. Lists and integers work in exactly the same way as far as assignment is concerned. If, in your second snippet, to assigned a different list to L in the penultimate line, the two variables would be unrelated.
But lists have an additional capability, which integers and strings don't have, which is that you can modify them. That's all you're​ seeing here.

Well M = L gets what L currently is and after it prints M you then append 6 to L but this will only effect L because M had only received what L previously was, but if you did M = L again after the append it would print the updated version of the list. Basically if you get a xvariable set to a yvariable and then that yvariable updates the xvariable will not update because you will have to update the xvariable again but this is usually this happens by it self if a loop is being used

Related

Python - leave only n items in a list

There are many ways to remove n items from the list, but I couldn't find a way to keep n items.
lst = ["ele1", "ele2", "ele3", "ele4", "ele5", "ele6", "ele7", "ele8", "ele9", "ele10"]
n = 5
lst = lst[:len(lst)-(len(lst)-n)]
print(lst)
So I tried to solve it in the same way as above, but the problem is that the value of 'lst' always changes in the work I am trying to do, so that method is not valid.
I want to know how to leave only n elements in a list and remove all elements after that.
The simplest/fastest solution is:
del lst[n:]
which tells it to delete any elements index n or above (implicitly keeping 0 through n - 1, a total of n elements).
If you must preserve the original list (e.g. maybe you received it as an argument, and it's poor form to change what they passed you most of the time), you can just reverse the approach (slice out what you want to keep, rather than remove what you want to discard) and do:
truncated = lst[:n] # You have access to short form and long form
so you have both the long and short form, or if you don't need the original list anymore, but it might be aliased elsewhere and you want the aliases unmodified:
lst = lst[:n] # Replaces your local alias, but leaves other aliases unchanged
very similar to the solution of ShadowRanger, but using a slice assignment on the left hand side of the assigment oparator:
lst[n:] = []

When a value of a sliced list "copy" is overwritten, the original list does not change. Why? (Python)

I come from R and am trying to get a better grip on mutability. Below is code, the first two parts of which I think I understand (see comments). I do not understand the third part.
#1. Refering to same instance with two variable names
listOrig = [i for i in range(1001, 1011)]
listCopy = listOrig
listOrig[0]=999
listOrig == listCopy #Returns True, because both variable names actually refer
#to the same instance, ergo still containing the same values
listOrig[0] is listCopy[0] #Same instance 999, the id is also the same as a
#consequence
#2. Refering to same part of original list through slicing
listSlice = listOrig[0:5]
listOrig[0] is listSlice[0] #Returns True, analogous to above
a = 999
listOrig[0] == a #True because it's the same value or number
listOrig[0] is a #False because they are different instances with different IDs
#3. WHAT I DO NOT UNDERSTAND: changing the sliced copy does not affect the original list
listOrig
listSlice
listSlice[0] = 1001
listOrig[0] is listSlice[0] #Different number, thus also obviously different ID
I am going to post an answer just because I think this is better explained with diagrams.
Suppose you create a list list_1 containing the numbers 1, 2 and 3.
list_1 = [1,2,3]
Then you assign the list to a new variable.
list_2 = list_1
It is another reference to the same original list.
Alternatively, suppose you take a slice of your list.
list_3 = list_1[:]
It is a new list with references to the same objects.
So if you reassign an element in the new list, it does not affect the original.
list_3[0] = 4
As is said in the comments, the slice is a copy of the original list. It currently (when the values are same) refers to same memory address and hence the id (with e.g. id-function) is the same and listOrig[0] is listCopy[0] is True.
Below example formulates this: even if you set two different integer variables b and c to be the same value, their ids are same. However, if you change the value of b, the value of c does not change. You can also check print(id(listSlice), id(listOrig[0:5])) from your example which shows, that the ids of the slice-variable and the original slice are different
a = 1
b = 2
c = 2
print(id(a))
print(id(b))
print(id(c))
print(b is c)
The output would be e.g.
2770111129904
2770111129936
2770111129936
True

Why doesn't assigning to the loop variable modify the original list? How can I assign back to the list in a loop? [duplicate]

This question already has answers here:
How to modify list entries during for loop?
(10 answers)
Closed 5 months ago.
When I try this code:
bar = [1,2,3]
print(bar)
for foo in bar:
print(id(foo))
foo = 0
print(id(foo))
print(bar)
I get this result:
[1, 2, 3]
5169664
5169676
5169652
5169676
5169640
5169676
[1, 2, 3]
I expected the end result to be [0,0,0] and that id would return identical values for each iteration. Why does it behave like this? How can I elegantly assign back to the elements of the list, without using enumerate or range(len(bar))?
See also: How to change variables fed into a for loop in list form
First of all, you cannot reassign a loop variable—well, you can, but that won’t change the list you are iterating over. So setting foo = 0 will not change the list, but only the local variable foo (which happens to contain the value for the iteration at the begin of each iteration).
Next thing, small numbers, like 0 and 1 are internally kept in a pool of small integer objects (This is a CPython implementation detail, doesn’t have to be the case!) That’s why the ID is the same for foo after you assign 0 to it. The id is basically the id of that integer object 0 in the pool.
If you want to change your list while iterating over it, you will unfortunately have to access the elements by index. So if you want to keep the output the same, but have [0, 0, 0] at the end, you will have to iterate over the indexes:
for i in range(len(bar)):
print id(bar[i])
bar[i] = 0
print id(bar[i])
print bar
Otherwise, it’s not really possible, because as soon as you store a list’s element in a variable, you have a separate reference to it that is unlinked to the one stored in the list. And as most of those objects are immutable and you create a new object when assigning a new value to a variable, you won’t get the list’s reference to update.
Yes, the output you got is the ordinary Python behavior. Assigning a new value to foo will change foo's id, and not change the values stored in bar.
If you just want a list of zeroes, you can do:
bar = [0] * len(bar)
If you want to do some more complicated logic, where the new assignment depends on the old value, you can use a list comprehension:
bar = [x * 2 for x in bar]
Or you can use map:
def double(x):
return x * 2
bar = map(double, bar)
you actually didnt change the list at all.
the first thing for loop did was to assign bar[0] to foo(equivalent to foo = bar[0]). foo is just an reference to 1. Then you assign another onject 0 to foo. This changed the reference of foo to 0. But you didnt change bar[0]. Remember, foo as a variable, references bar[0], but assign another value/object to foo doesn't affect bar[0] at all.
bar = [0 for x in bar]
Long answer : foo is just a local name, rebinding does not impact the list. Python variables are really just key:value pairs, not symbolic names for memory locations.

Python LIST functions not returning new lists [duplicate]

This question already has answers here:
Why do these list operations (methods: clear / extend / reverse / append / sort / remove) return None, rather than the resulting list?
(6 answers)
Closed 5 months ago.
I'm having an issue considering the built-in Python List-methods.
As I learned Python, I always thought Python mutators, as any value class mutators should do, returned the new variable it created.
Take this example:
a = range(5)
# will give [0, 1, 2, 3, 4]
b = a.remove(1)
# as I learned it, b should now be [0, 2, 3, 4]
# what actually happens:
# a = [0, 2, 3, 4]
# b = None
The main problem with this list mutator not returning a new list, is that you cannot to multiple mutations subsequently.
Say I want a list ranging from 0 to 5, without the 2 and the 3.
Mutators returning new variables should be able to do it like this:
a = range(5).remove(2).remove(3)
This sadly isn't possible, as range(5).remove(2) = None.
Now, is there a way to actually do multiple mutations on lists like I wanna do in my example? I think even PHP allows these types of subsequent mutations with Strings.
I also can't find a good reference on all the built-in Python functions. If anyone can find the actual definition (with return values) of all the list mutator methods, please let me know. All I can find is this page: http://docs.python.org/tutorial/datastructures.html
Rather than both mutating and returning objects, the Python library chooses to have just one way of using the result of a mutator. From import this:
There should be one-- and preferably only one --obvious way to do it.
Having said that, the more usual Python style for what you want to do is using list comprehensions or generator expressions:
[x for x in range(5) if x != 2 and x != 3]
You can also chain these together:
>>> [x for x in (x for x in range(5) if x != 2) if x != 3]
[0, 1, 4]
The above generator expression has the added advantage that it runs in O(n) time because Python only iterates over the range() once. For large generator expressions, and even for infinite generator expressions, this is advantageous.
Many methods of list and other mutable types intentionally return None so that there is no question in your mind as to whether you are creating a new object or mutating an existing object. The only thing that could be happening is mutation since, if a new object were created, it would have to be returned by the method, and it is not returned.
As you may have noticed, the methods of str that edit the string do return the new string, because strings are immutable and a new string is always returned.
There is of course nothing at all keeping you from writing a list subclass that has the desired behavior on .append() et al, although this seems like rather a heavy hammer to swing merely to allow you to chain method calls. Also, most Python programmers won't expect that behavior, making your code less clear.
In Python, essentially all methods that mutate the object return None.
That's so you don't accidentally think you've got a new object as a result.
You mention
I think even PHP allows these types of subsequent mutations with Strings.
While I don't remember about PHP, with string manipulations, you can chain method calls, because strings are immutable, so all string methods return a new string, since they don't (can't) change the one you call them on.
>>> "a{0}b".format(3).center(5).capitalize().join('ABC').swapcase()
'a A3B b A3B c'
Neither Python or php have built-in "mutators". You can simply write multiple lines, like
a = list(range(5))
a.remove(2)
a.remove(3)
If you want to generate a new list, copy the old one beforehand:
a = list(range(5))
b = a[:]
b.remove(2)
Note that in most cases, a singular call to remove indicates you should have used a set in the first place.
To remove multiple mutations on lists as you want to do on your example, you can do :
a = range(5)
a = [i for j, i in enumerate(a) if j not in [2, 3]]
a will be [0, 1, 4]

Sharing data between two lists

First let me say that I thought that the way data storage worked in python is that everything is an object so there is no need for such things as pointers. If you pass an piece of data into a function then that function has the real piece of data. When you exit out of that function it the data passed in could have been modified.
Now I was working with lists and I thought that if I put the same piece of data on two lists then modifying it in one place would modify it in the other.
How can I have one piece of data that is on two, or more, different lists? I would want to change this data in one place and then have the other change.
For example take the following:
p = 9
d = []
f = []
d.append(p)
f.append(p)
print 'd',d
print 'f',f
p = 3
print 'd',d
print 'f',f
When this is run the output is:
d [9]
f [9]
d [9]
f [9]
I would like the second set of data to be 3 but it doesn't seem to work. So where in my thought process did I go wrong? Is there an implicit copy operation when putting data onto a list?
First of all, integers (really, number objects) are immutable. There is no "modifying" this "data".
Second of all, there is also in Python the notion of name binding. When you do:
p = 9
Two things happen: first, a number object (9) is created; second, it is then bound to the name p.
When you later do:
p = 3
It does not, as you think, "modify" the immutable number object created earlier. What this simply does is, again, two things: first, a new number object (3) is created; second, it is then bound to the name p.
Pictorally:
(1a) 9
(1b) p --> 9
(2a) p --> 9
3
(2b) 9
p --> 3
Your second assignment doesn't change the value of the object bound to p, it rebinds p to a whole new object. d and f still contain references to the old object, which still has the same value.

Categories

Resources