why mutable objects having same value have different id in Python - python

Thank you for your valuable time, I have just started learning Python. I came across Mutable and Immutable objects.
As far as I know mutable objects can be changed after their creation.
a = [1,2,3]
print(id(a))
45809352
a = [3,2,1]
print(id(a))
52402312
Then why id of the same list "a" gets changed when its values are changed.

your interpretation is incorrect.
When you assign a new list to a, you change its reference.
On the other hand you could do:
a[:] = [3,2,1]
and then the reference would not change.

mutable means that the content of the object is changed. for example a.append(4) actually make a equal to [1, 2, 3, 4], while on the contrary, appending to a string (which is immutable) does not change it, it creates a new one.
However, when you re-assign, you create a new object and assign it to a, you don't alter the existing content of a. The previous content is lost (unless refered-to by some other variable)

If you change a list, its id doesn't change. But you may do things that instead create a new list, and then it will also have a new id.
E.g.,
>>> l=[]
>>> id(l)
140228658969920
>>> l.append(3) # Changes l
>>> l
[3]
>>> id(l)
140228658969920 # Same ID
>>> l = l + [4] # Computes a new list that is the result of l + [4], assigns that
>>> l
[3, 4]
>>> id(l)
140228658977608 # ID changed

When you do
a = [3, 2, 1]
You unlink the list of [1, 2, 3] from variable a.
Create a new list [3, 2, 1] then assign it to a variable.

Being immutable doesn't mean you assign a new object, it means your original object can be changed "in place" for example via .append()
>>> my_list = [1,2,3]
>>> id(my_list)
140532335329544
>>> my_list.append(5)
>>> id(my_list)
140532335329544
>>> my_list[3] = 4
>>> my_list
[1, 2, 3, 4]
>>> id(my_list)
140532335329544

Related

Difference between list.pop() and list = list[:-1]

>>> a = [1,2,3]
>>> a.pop()
3
>>> a
[1, 2]
>>> a = [1,2,3]
>>> a = a[:-1]
>>> a
[1, 2]
>>>
Is there any difference between the above methods to remove the last element from a list?
Yes. pop is O(1) and mutates the original list, while slice is O(n) and creates a copy of the list. Less formally, the pop method is an operation on the element at the end of the list and is defined in CPython as a call to list_resize(self, Py_SIZE(self) - 1);. This doesn't traverse the entire structure.
On the other hand, list_slice allocates a new list and loops over the entries in the old list ranging from the beginning to the end - 1, copying references to each item to the new list.
If what you're trying to do is remove the last element of the list, use pop or del a[-1].
pop do not change the id, just pop one item of list.
[:-1] is slice operation, which create a new list from old list.
>>> a = [1,2,3]
print(id(a))
>>> a.pop()
3
print(id(a))
>>> a
[1, 2]
>>> a = [1,2,3]
>>> a = a[:-1]
>>> a
print(id(a))
[1, 2]
>>>
id output (the number is not important, same or not same is key point):
4470627464
4470627464
4474450952
the pop method returns the last item from the list that it removes.
for example:
a = [1,2,3,4]
b = a.pop()
print(b) # 4
Also, using slicing, you are making a copy of the old list, whereas with using pop the list reference remains the same.
There is an basic difference thats occurs using in functions . Using[:-1] unchanged the original list but pop() can do.
a = [1,2,3]
b = [1,2,3]
def functionb(list):
list = list[:-1]
return list
def withpop(list):
return list.pop()
functionb(b)
withpop(a)
print b
print a
Will printed:
[1, 2, 3]
[1, 2]
Second is execution time . pop() is faster than [:-1] Because when you use [:-1]you have to overwrite to list.Lets say you have thousands values in index so it will be slowly than pop()
The way you have presented them, there's no outward difference. The pop instruction gives the interpreter an easier time of optimizing the instruction, as it can merely decrement the length attribute of the list. The -1 assignment will construct a new list, assign that to a, and then leave the old one for garbage collection.
There is a huge difference in aliasing: if you assigned something else to that list, you will get side effects with pop. For instance:
>>> a = [1, 2, 3, 4]
>>> b = a
>>> b
[1, 2, 3, 4]
>>> a.pop()
4
>>> b
[1, 2, 3]
>>> a = a[:-1]
>>> b
[1, 2, 3]
>>> a
[1, 2]
Yes there is difference.
when you use a.pop() you remove from list too
when you use a[:-1] object list not change
check with len(a)
>>> a = [1,2,3]
>>> a
[1, 2, 3]
>>> a[:-1]
[1, 2]
>>> len(a[:-1])
2
>>> a.pop()
3
>>> a
[1, 2]
>>> len(a)
2
>>>

Why does Python react differently on assigning matrix element depending on how you build the matrix? [duplicate]

I create a list of lists and want to append items to the individual lists, but when I try to append to one of the lists (a[0].append(2)), the item gets added to all lists.
a = []
b = [1]
a.append(b)
a.append(b)
a[0].append(2)
a[1].append(3)
print(a)
Gives: [[1, 2, 3], [1, 2, 3]]
Whereas I would expect: [[1, 2], [1, 3]]
Changing the way I construct the initial list of lists, making b a float instead of a list and putting the brackets inside .append(), gives me the desired output:
a = []
b = 1
a.append([b])
a.append([b])
a[0].append(2)
a[1].append(3)
print(a)
Gives: [[1, 2], [1, 3]]
But why? It is not intuitive that the result should be different. I know this has to do with there being multiple references to the same list, but I don't see where that is happening.
It is because the list contains references to objects. Your list doesn't contain [[1 2 3] [1 2 3]], it is [<reference to b> <reference to b>].
When you change the object (by appending something to b), you are changing the object itself, not the list that contains the object.
To get the effect you desire, your list a must contain copies of b rather than references to b. To copy a list you can use the range [:]. For example:
>>> a = []
>>> b = [1]
>>> a.append(b[:])
>>> a.append(b[:])
>>> a[0].append(2)
>>> a[1].append(3)
>>> print a
[[1, 2], [1, 3]]
The key is this part:
a.append(b)
a.append(b)
You are appending the same list twice, so both a[0] and a[1] are references to the same list.
In your second example, you are creating new lists each time you call append like a.append([b]), so they are separate objects that are initialized with the same float value.
In order to make a shallow copy of a list, the idiom is
a.append(b[:])
which when doubled will cause a to have two novel copies of the list b which will not give you the aliasing bug you report.

What's the difference between a[] and a[:] when assigning values?

I happen to see this snippet of code:
a = []
a = [a, a, None]
# makes a = [ [], [], None] when print
a = []
a[:] = [a, a, None]
# makes a = [ [...], [...], None] when print
It seems the a[:] assignment assigns a pointer but I can't find documents about that. So anyone could give me an explicit explanation?
In Python, a is a name - it points to an object, in this case, a list.
In your first example, a initially points to the empty list, then to a new list.
In your second example, a points to an empty list, then it is updated to contain the values from the new list. This does not change the list a references.
The difference in the end result is that, as the right hand side of an operation is evaluated first, in both cases, a points to the original list. This means that in the first case, it points to the list that used to be a, while in the second case, it points to itself, making a recursive structure.
If you are having trouble understanding this, I recommend taking a look at it visualized.
The first will point a to a new object, the second will mutate a, so the list referenced by a is still the same.
For example:
a = [1, 2, 3]
b = a
print b # [1, 2, 3]
a[:] = [3, 2, 1]
print b # [3, 2, 1]
a = [1, 2, 3]
#b still references to the old list
print b # [3, 2, 1]
More clear example from #pythonm response
>>> a=[1,2,3,4]
>>> b=a
>>> c=a[:]
>>> a.pop()
4
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> c
[1, 2, 3, 4]
>>>

List assignment with [:]

What's the difference between
lst = range(100)
and
lst[:] = range(100)
Before that assignment the lst variable was already assigned to a list:
lst = [1, 2, 3]
lst = range(100)
or
lst = [1, 2, 3]
lst[:] = range(100)
When you do
lst = anything
You're pointing the name lst at an object. It doesn't change the old object lst used to point to in any way, though if nothing else pointed to that object its reference count will drop to zero and it will get deleted.
When you do
lst[:] = whatever
You're iterating over whatever, creating an intermediate tuple, and assigning each item of the tuple to an index in the already existing lst object. That means if multiple names point to the same object, you will see the change reflected when you reference any of the names, just as if you use append or extend or any of the other in-place operations.
An example of the difference:
>>> lst = range(1, 4)
>>> id(lst)
74339392
>>> lst = [1, 2, 3]
>>> id(lst) # different; you pointed lst at a new object
73087936
>>> lst[:] = range(1, 4)
>>> id(lst) # the same, you iterated over the list returned by range
73087936
>>> lst = xrange(1, 4)
>>> lst
xrange(1, 4) # not a list, an xrange object
>>> id(lst) # and different
73955976
>>> lst = [1, 2, 3]
>>> id(lst) # again different
73105320
>>> lst[:] = xrange(1, 4) # this gets read temporarily into a tuple
>>> id(lst) # the same, because you iterated over the xrange
73105320
>>> lst # and still a list
[1, 2, 3]
When it comes to speed, slice assignment is slower. See Python Slice Assignment Memory Usage for more information about its memory usage.
The first one redefines the built-in name list to point to some list.
The second fails with TypeError: 'type' object does not support item assignment.
list[:] will only work if there is already an object named list that allows slice assignment.
Also, you shouldn't name variables list because there is a built-in named list which is the list type itself.
list[:] specifies a range within the list, in this case it defines the complete range of the list, i.e. the whole list and changes them. list=range(100), on the other hand, kind of wipes out the original contents of list and sets the new contents.
But try the following:
a=[1,2,3,4]
a[0:2]=[5,6]
a # prints [5,6,3,4]
You see, we changed the first two elements with the assignment. This means, using this notation, you can change several elements in the list once.
[:] is also useful to make a deep copy of the list.
def x(l):
f=l[:]
g=l
l.append(8)
print "l", l
print "g", g
print "f", f
l = range(3)
print l
#[0, 1, 2]
x(l)
#l [0, 1, 2, 8]
#g [0, 1, 2, 8]
#f [0, 1, 2]
print l
#[0, 1, 2, 8]
Modification to l is get reflected in g (because, both point to same list, in fact, both g and l are just names in python), not in f(because, it's a copy of l)
But, in your case, It doesn't make any difference. (Though, I'm not eligible to comment on any memory usage of both methods.)
Edit
h = range(3)
id(h) #141312204
h[:]=range(3)
id(h) #141312204
h=range(3)
id(h) #141312588
list[:] = range(100) updates the list
list = range(100) creates new list.
#agf: thanks for pointing my error
list[:] = range(100)
won't work on uninitialized variable, as it is modifying it. The [:] specifies the whole list/touple.

Why does foo.append(bar) affect all elements in a list of lists?

I create a list of lists and want to append items to the individual lists, but when I try to append to one of the lists (a[0].append(2)), the item gets added to all lists.
a = []
b = [1]
a.append(b)
a.append(b)
a[0].append(2)
a[1].append(3)
print(a)
Gives: [[1, 2, 3], [1, 2, 3]]
Whereas I would expect: [[1, 2], [1, 3]]
Changing the way I construct the initial list of lists, making b a float instead of a list and putting the brackets inside .append(), gives me the desired output:
a = []
b = 1
a.append([b])
a.append([b])
a[0].append(2)
a[1].append(3)
print(a)
Gives: [[1, 2], [1, 3]]
But why? It is not intuitive that the result should be different. I know this has to do with there being multiple references to the same list, but I don't see where that is happening.
It is because the list contains references to objects. Your list doesn't contain [[1 2 3] [1 2 3]], it is [<reference to b> <reference to b>].
When you change the object (by appending something to b), you are changing the object itself, not the list that contains the object.
To get the effect you desire, your list a must contain copies of b rather than references to b. To copy a list you can use the range [:]. For example:
>>> a = []
>>> b = [1]
>>> a.append(b[:])
>>> a.append(b[:])
>>> a[0].append(2)
>>> a[1].append(3)
>>> print a
[[1, 2], [1, 3]]
The key is this part:
a.append(b)
a.append(b)
You are appending the same list twice, so both a[0] and a[1] are references to the same list.
In your second example, you are creating new lists each time you call append like a.append([b]), so they are separate objects that are initialized with the same float value.
In order to make a shallow copy of a list, the idiom is
a.append(b[:])
which when doubled will cause a to have two novel copies of the list b which will not give you the aliasing bug you report.

Categories